volute 是什么?
volute(蜗壳)是一个使用 Raspberry Pi+ 制作的语音助手.
什么是树莓派?
树莓派(英语:Raspberry Pi)是基于 Linux 的单片机电脑,由英国树莓派基金会开发,目的是以低价硬件及自由软件促进学校的基本计算机科学教育。
树莓派每一代均使用博通(Broadcom)出产的 ARM 架构处理器,如今生产的机型内存在 2GB 和 8GB 之间,主要使用 SD 卡或者 TF 卡作为存储媒体,配备 USB 接口、HDMI 的视频输出(支持声音输出)和 RCA 端子输出,内置 Ethernet/WLAN/Bluetooth 网络链接的方式(依据型号决定),并且可使用多种操作系统。产品线型号分为 A 型、B 型、Zero 型和 ComputeModule 计算卡。
简单的说,这是一台可以放到口袋里的电脑!!
什么是 ?
是一个能执行 Javascript 的环境,一个事件驱动 I/O 的 Javascript 环境,基于 Google 的 V8 引擎.
什么是人机对话系统 ?
人机对话(Human-Machine Conversation)是指让机器理解和运用自然语言实现人机通信的技术。
对话系统大致可分为 5 个基本模块:语音识别(ASR)、自然语音理解(NLU)、对话管理(DM)、自然语言生成(NLG)、语音合成(TTS)。
- 语音识别(ASR):完成语音到文本的转换,将用户说话的声音转化为语音。
- 自然语言理解(NLU):完成对文本的语义解析,提取关键信息,进行意图识别与实体识别。
- 对话管理(DM):负责对话状态维护、数据库查询、上下文管理等。
- 自然语言生成(NLG):生成相应的自然语言文本。
- 语音合成(TTS):将生成的文本转换为语音。
材料准备
- 树莓派 4B 主板
- 树莓派 5V3A TYPE C 接口
- 微型 USB 麦克风
- 迷你音箱
- 16G TF 卡
- 川宇读卡器
- 杜邦线,外壳,散热片...
树莓派系统安装及基础配置
新的树莓派不像你买的 Macbook Pro 一样开机就能用 ,想要顺利体验树莓派,还得一步一步来~
烧录操作系统
树莓派没有硬盘结构,仅有一个 micro SD 卡插槽用于存储,因此要把操作系统装到 micro SD 卡中。
树莓派支持许多操作系统,这里选择的是官方推荐的 Raspbian,这是一款基于 Debian Linux 的树莓派专用系统,适用于树莓派所有的型号。
安装系统我用的是 Raspberry Pi Imager 工具为树莓派烧录系统镜像。
基础配置
要对树莓派进行配置,首先要启动系统(我们安装的是系统镜像,可免安装直接进入),然后将树莓派连接显示器即可看到系统桌面,我这里使用的是另一种方法:
- 使用 IP Scanner 工具 扫描出 Raspberry Pi 的 IP
- 扫描出 IP 后使用 VNC Viewer 工具 连接进系统
- 也可以直接 ssh 连接,然后通过 raspi-config 命令进行配置
volute 实现思路
任务调度服务
const fs = require("fs");
const path = require("path");
const Speaker = require("speaker");
const { record } = require("node-record-lpcm16");
const XunFeiIAT = require("./services/");
const XunFeiTTS = require("./services/");
const initSnowboy = require("./services/");
const TulingBotService = require("./services/");
// 任务调度服务
const taskScheduling = {
// 麦克风
mic: null,
speaker: null,
detector: null,
// 音频输入流
inputStream: null,
// 音頻輸出流
outputStream: null,
init() {
// 初始化snowboy
= initSnowboy({
record: (this),
stopRecord: (this),
});
// 管道流,将麦克风接收到的流传递给snowboy
();
},
start() {
// 监听麦克风输入流
this.mic = record({
sampleRate: 16000, // 采样率
threshold: ,
verbose: true,
recordProgram: "arecord",
}).stream();
();
},
// 记录音频输入
recordSound() {
// 每次记录前,先停止上次未播放完成的输出流
();
("start record");
// 创建可写流
= fs.createWriteStream(
(__dirname, "./assets/"),
{
encoding: "binary",
}
);
// 管道流,将麦克风接受到的输入流 传递给 创建的可写流
();
},
// 停止音频输入
stopRecord() {
if () {
("stop record");
// 解绑绑定的管道流
();
();
(() => {
// 销毁输入流
.destroy();
= null;
// 重新初始化
();
// 调用语音听写服务
();
});
}
},
// speech to text
speech2Text() {
// 实例化 语音听写服务
const iatService = new XunFeiIAT({
onReply: (msg) => {
("msg", msg);
// 回调,调用聊天功能
(msg);
},
});
();
},
// 聊天->图灵机器人
onChat(text) {
// 实例化聊天机器人
(text).then((res) => {
(res);
// 接收到聊天消息,调用语音合成服务
(res);
});
},
// text to speech
text2Speech(text) {
// 实例化 语音合成服务
const ttsService = new XunFeiTTS({
text,
onDone: () => {
("onDone");
();
},
});
();
},
// 播放,音频输出
onSpeak() {
// 实例化speaker,用于播放语音
= new Speaker({
channels: 1,
bitDepth: 16,
sampleRate: 16000,
});
// 创建可读流
this.outputStream = fs.createReadStream(
(__dirname, "./assets/")
);
// this is just to activate the speaker, 2s delay
.write((32000, 10));
// 管道流,将输出流传递给speaker进行播放
();
("end", () => {
this.outputStream = null;
= null;
});
},
// 停止播放
stopSpeak() {
this.outputStream && this.outputStream.unpipe();
},
};
();
热词唤醒 Snowboy
语音助手需要像市面上的设备一样,需要唤醒。 如果没有唤醒步骤,一直做监听的话,对存储资源和网络连接的需求是非常大的。
Snowboy 是一款高度可定制的唤醒词检测引擎(Hotwords Detection Library),可以用于实时嵌入式系统,通过训练热词之后,可以离线运行,并且 功耗很低。当前,它可以运行在 Raspberry Pi、(Ubuntu)Linux 和 Mac OS X 系统上。
const path = require("path");
const snowboy = require("snowboy");
const models = new snowboy.Models();
// 添加训练模型
({
file: (__dirname, "../configs/"),
sensitivity: "",
hotwords: "volute",
});
// 初始化 Detector 对象
const detector = new snowboy.Detector({
resource: (__dirname, "../configs/"),
models: models,
audioGain: 1.0,
applyFrontend: false,
});
/**
* 初始化 initSnowboy
* 实现思路:
* 1. 监听到热词,进行唤醒,开始录音
* 2. 录音期间,有声音时,重置silenceCount参数
* 3. 录音期间,未接受到声音时,对silenceCount进行累加,当累加值大于3时,停止录音
*/
function initSnowboy({ record, stopRecord }) {
const MAX_SILENCE_COUNT = 3;
let silenceCount = 0,
speaking = false;
/**
* silence事件回调,没声音时触发
*/
const onSilence = () => {
("silence");
if (speaking && ++silenceCount > MAX_SILENCE_COUNT) {
speaking = false;
stopRecord && stopRecord();
("silence", onSilence);
("sound", onSound);
("hotword", onHotword);
}
};
/**
* sound事件回调,有声音时触发
*/
const onSound = () => {
("sound");
if (speaking) {
silenceCount = 0;
}
};
/**
* hotword事件回调,监听到热词时触发
*/
const onHotword = (index, hotword, buffer) => {
if (!speaking) {
silenceCount = 0;
speaking = true;
record && record();
}
};
("silence", onSilence);
("sound", onSound);
("hotword", onHotword);
return detector;
}
module.exports = initSnowboy;
语音听写 科大讯飞 API
语音转文字使用的是讯飞开放平台的语音听写服务.它可以将短音频(≤60 秒)精准识别成文字,除中文普通话和英文外,支持 25 种方言和 12 个语种,实时返回结果,达到边说边返回的效果。
require("dotenv").config();
const fs = require("fs");
const WebSocket = require("ws");
const { resolve } = require("path");
const { createAuthParams } = require("../utils/auth");
class XunFeiIAT {
constructor({ onReply }) {
super();
// websocket 连接
this.ws = null;
// 返回结果,解析后的消息文字
= "";
= onReply;
// 需要进行转换的输入流 语音文件
= resolve(__dirname, "../assets/");
// 接口 入参
= {
host: "",
path: "/v2/iat",
apiKey: process.env.XUNFEI_API_KEY,
secret: process.env.XUNFEI_SECRET,
};
}
// 生成websocket连接
generateWsUrl() {
const { host, path } = ;
// 接口鉴权,参数加密
const params = createAuthParams();
return `ws://${host}${path}?${params}`;
}
// 初始化
init() {
const reqUrl = ();
this.ws = new WebSocket(reqUrl);
WsEvent();
}
// 初始化websocket事件
initWsEvent() {
("open", (this));
("error", );
("close", );
("message", (this));
}
/**
* websocket open事件,触发表示已成功建立连接
*/
onOpen() {
("open");
();
}
onPush(file) {
(file);
}
// websocket 消息接收 回调
onMessage(data) {
const payload = (data);
if (payload.data && payload.data.result) {
// 拼接消息结果
+= payload.data.result.ws.reduce(
(acc, item) => acc + ((cw) => ),
""
);
// status 2表示结束
if (payload.data.status === 2) {
();
}
}
}
// websocket 关闭事件
onClose() {
("close");
}
// websocket 错误事件
onError(error) {
(error);
}
/**
* 解析语音文件,将语音以二进制流的形式传送给后端
*/
pushAudioFile(audioFile) {
= "";
// 发送需要的载体参数
const audioPayload = (statusCode, audioBase64) => ({
common:
statusCode === 0
? {
app_id: "5f6cab72",
}
: undefined,
business:
statusCode === 0
? {
language: "zh_cn",
domain: "iat",
ptt: 0,
}
: undefined,
data: {
status: statusCode,
format: "audio/L16;rate=16000",
encoding: "raw",
audio: audioBase64,
},
});
const chunkSize = 9000;
// 创建buffer,用于存储二进制数据
const buffer = (chunkSize);
// 打开语音文件
(audioFile, "r", (err, fd) => {
if (err) {
throw err;
}
let i = 0;
// 以二进制流的形式递归发送
function readNextChunk() {
(fd, buffer, 0, chunkSize, null, (errr, nread) => {
if (errr) {
throw errr;
}
// nread表示文件流已读完,发送传输结束标识(status=2)
if (nread === 0) {
(
({
data: { status: 2 },
})
);
return fs.close(fd, (err) => {
if (err) {
throw err;
}
});
}
let data;
if (nread < chunkSize) {
data = (0, nread);
} else {
data = buffer;
}
const audioBase64 = ("base64");
const payload = audioPayload(i >= 1 ? 1 : 0, audioBase64);
((payload));
i++;
(this);
});
}
(this);
});
}
}
module.exports = XunFeiIAT;
聊天机器人 图灵机器人 API
图灵机器人 API V2.0 是基于图灵机器人平台语义理解、深度学习等核心技术,为广大开发者和企业提供的在线服务和开发接口。
目前 API 接口可调用聊天对话、语料库、技能三大模块的语料:
聊天对话是指平台免费提供的近 10 亿条公有对话语料,满足用户对话娱乐需求;
语料库是指用户在平台上传的私有语料,仅供个人查看使用,帮助用户最便捷的搭建专业领域次的语料。
技能服务是指平台打包的 26 种实用服务技能。涵盖生活、出行、购物等多个领域,一站式满足用户需求。
require("dotenv").config();
const axios = require("axios");
// 太简单了..懒得解释
const TulingBotService = {
requestUrl: "",
start(text) {
return new Promise((resolve) => {
axios
.post(, {
reqType: 0,
perception: {
inputText: {
text,
},
},
userInfo: {
apiKey: process.env.TULING_BOT_API_KEY,
userId: process.env.TULING_BOT_USER_ID,
},
})
.then((res) => {
// ((res.data, null, 2));
resolve([0].);
});
});
},
};
module.exports = TulingBotService;
语音合成 科大讯飞 API
语音合成流式接口将文字信息转化为声音信息,同时提供了众多极具特色的发音人(音库)供您选择。
该语音能力是通过 Websocket API 的方式给开发者提供一个通用的接口。Websocket API 具备流式传输能力,适用于需要流式数据传输的 AI 服务场景。相较于 SDK,API 具有轻量、跨语言的特点;相较于 HTTP API,Websocket API 协议有原生支持跨域的优势。
require("dotenv").config();
const fs = require("fs");
const WebSocket = require("ws");
const { resolve } = require("path");
const { createAuthParams } = require("../utils/auth");
class XunFeiTTS {
constructor({ text, onDone }) {
super();
this.ws = null;
// 要转换的文字
= text;
= onDone;
// 转换后的语音文件
= resolve(__dirname, "../assets/");
// 接口入参
= {
host: "",
path: "/v2/tts",
appid: process.env.XUNFEI_APP_ID,
apiKey: process.env.XUNFEI_API_KEY,
secret: process.env.XUNFEI_SECRET,
};
}
// 生成websocket连接
generateWsUrl() {
const { host, path } = ;
const params = createAuthParams();
return `ws://${host}${path}?${params}`;
}
// 初始化
init() {
const reqUrl = ();
(reqUrl);
this.ws = new WebSocket(reqUrl);
WsEvent();
}
// 初始化websocket事件
initWsEvent() {
("open", (this));
("error", );
("close", );
("message", (this));
}
/**
* websocket open事件,触发表示已成功建立连接
*/
onOpen() {
("open");
();
if (()) {
();
}
}
// 发送要转换的参数信息
onSend() {
const frame = {
// 填充common
common: {
app_id: .appid,
},
// 填充business
business: {
aue: "raw",
auf: "audio/L16;rate=16000",
vcn: "xiaoyan",
tte: "UTF8",
},
// 填充data
data: {
text: ().toString("base64"),
status: 2,
},
};
((frame));
}
// 保存转换后的语音结果
onSave(data) {
(, data, { flag: "a" });
}
// websocket 消息接收 回调
onMessage(data, err) {
if (err) return;
const res = (data);
if ( !== 0) {
();
return;
}
// 接收消息结果并进行保存
const audio = res.data.audio;
const audioBuf = (audio, "base64");
(audioBuf);
if ( == 0 && res.data.status == 2) {
();
();
}
}
onClose() {
("close");
}
onError(error) {
(error);
}
}
module.exports = XunFeiTTS;
效果演示
volute · 语雀源码地址
Github 源码地址 , 如果对你有帮助,请给个star~