Что такое волюта?
volute — голосовой помощник, созданный с помощью Raspberry Pi+Node.js.
Что такое малиновый пи?
Raspberry Pi (англ. Raspberry Pi) — это однокристальный компьютер на базе Linux, разработанный Raspberry Pi Foundation в Великобритании для продвижения базового компьютерного образования в школах с недорогим оборудованием и бесплатным программным обеспечением.
Каждое поколение Raspberry Pi использует процессор архитектуры ARM производства Broadcom.Сегодняшние модели имеют память от 2 ГБ до 8 ГБ, в основном используют SD-карты или TF-карты в качестве носителя и оснащены интерфейсами USB и видеовыходом HDMI.(Поддержка вывода звука ) и выход терминала RCA, встроенный сетевой канал Ethernet/WLAN/Bluetooth (в зависимости от модели) и могут использовать различные операционные системы. Модели продуктовой линейки делятся на вычислительные карты A-type, B-type, Zero-type и ComputeModule.
Проще говоря, это компьютер, который помещается в вашем кармане!
Что такое Node.js?
Первоначально Javascript мог выполняться только в среде браузера. Рождение Node.js позволяет нам использовать Javascript на стороне сервера. Node.js — это среда, которая может выполнять Javascript, управляемый событиями ввод-вывод на стороне сервера. Среда Javascript, основанная на движке Google V8.
Что такое диалоговая система человек-машина?
Человеко-машинное общение относится к технологии, которая позволяет машинам понимать и использовать естественный язык для осуществления коммуникации между человеком и машиной.
Диалоговую систему можно условно разделить на 5 основных модулей: распознавание речи (ASR), понимание естественной речи (NLU), управление диалогами (DM), генерация естественного языка (NLG) и синтез речи (TTS).
- Распознавание речи (ASR): полное преобразование речи в текст, преобразование голоса пользователя в речь.
- Понимание естественного языка (NLU): полный семантический анализ текста, извлечение ключевой информации и выполнение распознавания намерений и сущностей.
- Управление диалогом (DM): отвечает за поддержание состояния диалога, запросы к базе данных, управление контекстом и т. д.
- Генерация естественного языка (NLG): Генерация соответствующего текста на естественном языке.
- Синтез речи (TTS): преобразует сгенерированный текст в речь.
Подготовка материала
- Материнская плата Raspberry Pi 4B
- Интерфейс Raspberry Pi 5V3A TYPE C
- Микрофон USB
- мини-динамик
- 16G TF-карта
- кард-ридер Chuanyu
- Провод Dupont, корпус, радиатор...
Установка системы Raspberry Pi и базовая настройка
Новый Raspberry Pi не готов к использованию, как Macbook, который вы купили.
Сжечь операционную систему
Raspberry Pi не имеет структуры жесткого диска и имеет только слот для карты micro SD для хранения, поэтому операционная система должна быть установлена на карту micro SD.
Raspberry Pi поддерживает множество операционных систем, вот официально рекомендуемая Raspbian, специализированная система Raspberry Pi на основе Debian Linux, подходящая для всех моделей Raspberry Pi.
Для системы установки я использовал инструмент Raspberry Pi Imager, чтобы записать образ системы для Raspberry Pi.
Базовая конфигурация
Чтобы настроить Raspberry Pi, вы должны сначала запустить систему. Вы можете подключить Raspberry Pi к монитору, клавиатуре и мыши, чтобы увидеть рабочий стол системы. Я использую другой метод:
- Используйте инструмент IP Scanner для сканирования IP-адреса Raspberry Pi.
- После сканирования IP-адреса используйте инструмент VNC Viewer для подключения к системе.
- Вы также можете напрямую подключиться к ssh и настроить его с помощью команды raspi-config.
- Настройте такие параметры, как сеть/разрешение/язык/вход и выходной звук
идеи реализации улитки
сервис планирования задач
const fs = require("fs");
const path = require("path");
const Speaker = require("speaker");
const { record } = require("node-record-lpcm16");
const XunFeiIAT = require("./services/xunfeiiat.service");
const XunFeiTTS = require("./services/xunfeitts.service");
const initSnowboy = require("./services/snowboy.service");
const TulingBotService = require("./services/tulingbot.service");
// 任务调度服务
const taskScheduling = {
// 麦克风
mic: null,
speaker: null,
detector: null,
// 音频输入流
inputStream: null,
// 音頻輸出流
outputStream: null,
init() {
// 初始化snowboy
this.detector = initSnowboy({
record: this.recordSound.bind(this),
stopRecord: this.stopRecord.bind(this),
});
// 管道流,将麦克风接收到的流传递给snowboy
this.mic.pipe(this.detector);
},
start() {
// 监听麦克风输入流
this.mic = record({
sampleRate: 16000, // 采样率
threshold: 0.5,
verbose: true,
recordProgram: "arecord",
}).stream();
this.init();
},
// 记录音频输入
recordSound() {
// 每次记录前,先停止上次未播放完成的输出流
this.stopSpeak();
console.log("start record");
// 创建可写流
this.inputStream = fs.createWriteStream(
path.resolve(__dirname, "./assets/input.wav"),
{
encoding: "binary",
}
);
// 管道流,将麦克风接受到的输入流 传递给 创建的可写流
this.mic.pipe(this.inputStream);
},
// 停止音频输入
stopRecord() {
if (this.inputStream) {
console.log("stop record");
// 解绑this.mac绑定的管道流
this.mic.unpipe(this.inputStream);
this.mic.unpipe(this.detector);
process.nextTick(() => {
// 销毁输入流
this.inputStream.destroy();
this.inputStream = null;
// 重新初始化
this.init();
// 调用语音听写服务
this.speech2Text();
});
}
},
// speech to text
speech2Text() {
// 实例化 语音听写服务
const iatService = new XunFeiIAT({
onReply: (msg) => {
console.log("msg", msg);
// 回调,调用聊天功能
this.onChat(msg);
},
});
iatService.init();
},
// 聊天->图灵机器人
onChat(text) {
// 实例化聊天机器人
TulingBotService.start(text).then((res) => {
console.log(res);
// 接收到聊天消息,调用语音合成服务
this.text2Speech(res);
});
},
// text to speech
text2Speech(text) {
// 实例化 语音合成服务
const ttsService = new XunFeiTTS({
text,
onDone: () => {
console.log("onDone");
this.onSpeak();
},
});
ttsService.init();
},
// 播放,音频输出
onSpeak() {
// 实例化speaker,用于播放语音
this.speaker = new Speaker({
channels: 1,
bitDepth: 16,
sampleRate: 16000,
});
// 创建可读流
this.outputStream = fs.createReadStream(
path.resolve(__dirname, "./assets/output.wav")
);
// this is just to activate the speaker, 2s delay
this.speaker.write(Buffer.alloc(32000, 10));
// 管道流,将输出流传递给speaker进行播放
this.outputStream.pipe(this.speaker);
this.outputStream.on("end", () => {
this.outputStream = null;
this.speaker = null;
});
},
// 停止播放
stopSpeak() {
this.outputStream && this.outputStream.unpipe(this.speaker);
},
};
taskScheduling.start();
Горячие слова будят Снежка
Голосовые помощники должны просыпаться так же, как устройства на рынке. Если шаг пробуждения отсутствует, а мониторинг выполняется постоянно, потребность в ресурсах хранения и сетевом подключении очень велика.
Snowboy — это настраиваемый механизм обнаружения слов пробуждения (библиотека обнаружения горячих слов), который можно использовать во встроенных системах реального времени.После обучения горячих слов он может работать в автономном режиме с низким энергопотреблением. В настоящее время он работает на системах Raspberry Pi, (Ubuntu) Linux и Mac OS X.
const path = require("path");
const snowboy = require("snowboy");
const models = new snowboy.Models();
// 添加训练模型
models.add({
file: path.resolve(__dirname, "../configs/volute.pmdl"),
sensitivity: "0.5",
hotwords: "volute",
});
// 初始化 Detector 对象
const detector = new snowboy.Detector({
resource: path.resolve(__dirname, "../configs/common.res"),
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 = () => {
console.log("silence");
if (speaking && ++silenceCount > MAX_SILENCE_COUNT) {
speaking = false;
stopRecord && stopRecord();
detector.off("silence", onSilence);
detector.off("sound", onSound);
detector.off("hotword", onHotword);
}
};
/**
* sound事件回调,有声音时触发
*/
const onSound = () => {
console.log("sound");
if (speaking) {
silenceCount = 0;
}
};
/**
* hotword事件回调,监听到热词时触发
*/
const onHotword = (index, hotword, buffer) => {
if (!speaking) {
silenceCount = 0;
speaking = true;
record && record();
}
};
detector.on("silence", onSilence);
detector.on("sound", onSound);
detector.on("hotword", onHotword);
return detector;
}
module.exports = initSnowboy;
Голосовая диктовка iFLYTEK API
Преобразование речи в текст использует службу голосовой диктовки открытой платформы iFLYTEK. Он может точно распознавать короткие аудио (≤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;
// 返回结果,解析后的消息文字
this.message = "";
this.onReply = onReply;
// 需要进行转换的输入流 语音文件
this.inputFile = resolve(__dirname, "../assets/input.wav");
// 接口 入参
this.params = {
host: "iat-api.xfyun.cn",
path: "/v2/iat",
apiKey: process.env.XUNFEI_API_KEY,
secret: process.env.XUNFEI_SECRET,
};
}
// 生成websocket连接
generateWsUrl() {
const { host, path } = this.params;
// 接口鉴权,参数加密
const params = createAuthParams(this.params);
return `ws://${host}${path}?${params}`;
}
// 初始化
init() {
const reqUrl = this.generateWsUrl();
this.ws = new WebSocket(reqUrl);
this.initWsEvent();
}
// 初始化websocket事件
initWsEvent() {
this.ws.on("open", this.onOpen.bind(this));
this.ws.on("error", this.onError);
this.ws.on("close", this.onClose);
this.ws.on("message", this.onMessage.bind(this));
}
/**
* websocket open事件,触发表示已成功建立连接
*/
onOpen() {
console.log("open");
this.onPush(this.inputFile);
}
onPush(file) {
this.pushAudioFile(file);
}
// websocket 消息接收 回调
onMessage(data) {
const payload = JSON.parse(data);
if (payload.data && payload.data.result) {
// 拼接消息结果
this.message += payload.data.result.ws.reduce(
(acc, item) => acc + item.cw.map((cw) => cw.w),
""
);
// status 2表示结束
if (payload.data.status === 2) {
this.onReply(this.message);
}
}
}
// websocket 关闭事件
onClose() {
console.log("close");
}
// websocket 错误事件
onError(error) {
console.log(error);
}
/**
* 解析语音文件,将语音以二进制流的形式传送给后端
*/
pushAudioFile(audioFile) {
this.message = "";
// 发送需要的载体参数
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 = Buffer.alloc(chunkSize);
// 打开语音文件
fs.open(audioFile, "r", (err, fd) => {
if (err) {
throw err;
}
let i = 0;
// 以二进制流的形式递归发送
function readNextChunk() {
fs.read(fd, buffer, 0, chunkSize, null, (errr, nread) => {
if (errr) {
throw errr;
}
// nread表示文件流已读完,发送传输结束标识(status=2)
if (nread === 0) {
this.ws.send(
JSON.stringify({
data: { status: 2 },
})
);
return fs.close(fd, (err) => {
if (err) {
throw err;
}
});
}
let data;
if (nread < chunkSize) {
data = buffer.slice(0, nread);
} else {
data = buffer;
}
const audioBase64 = data.toString("base64");
const payload = audioPayload(i >= 1 ? 1 : 0, audioBase64);
this.ws.send(JSON.stringify(payload));
i++;
readNextChunk.call(this);
});
}
readNextChunk.call(this);
});
}
}
module.exports = XunFeiIAT;
Чат-бот Turing Bot API
Turing Robot API V2.0 — это онлайн-сервис и интерфейс разработки, предоставляемый разработчикам и предприятиям на основе основных технологий, таких как семантическое понимание и глубокое изучение платформы Turing Robot.
В настоящее время интерфейс API может вызывать корпус из трех модулей: чат-диалог, корпус и навыки:
Диалог в чате относится к почти 1 миллиарду материалов для публичного диалога, бесплатно предоставляемых платформой для удовлетворения потребностей пользователей в диалоге и развлечениях;
Корпус относится к частному корпусу, загруженному пользователем на платформу, который предназначен только для личного просмотра и использования, помогая пользователю создать наиболее удобный корпус в профессиональной сфере.
Сервис навыков относится к 26 практическим навыкам обслуживания, упакованным платформой. Охватывая жизнь, путешествия, шопинг и другие области, универсальное решение для удовлетворения потребностей пользователей.
require("dotenv").config();
const axios = require("axios");
// 太简单了..懒得解释 🐶
const TulingBotService = {
requestUrl: "http://openapi.tuling123.com/openapi/api/v2",
start(text) {
return new Promise((resolve) => {
axios
.post(this.requestUrl, {
reqType: 0,
perception: {
inputText: {
text,
},
},
userInfo: {
apiKey: process.env.TULING_BOT_API_KEY,
userId: process.env.TULING_BOT_USER_ID,
},
})
.then((res) => {
// console.log(JSON.stringify(res.data, null, 2));
resolve(res.data.results[0].values.text);
});
});
},
};
module.exports = TulingBotService;
Синтез речи iFLYTEK API
Потоковый интерфейс синтеза речи преобразует текстовую информацию в звуковую и предоставляет вам на выбор множество различных динамиков (звуковых библиотек).
Эта голосовая возможность предоставляет разработчикам общий интерфейс через Websocket API. Websocket API имеет возможность потоковой передачи и подходит для сценариев службы ИИ, требующих потоковой передачи данных. По сравнению с 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;
// 要转换的文字
this.text = text;
this.onDone = onDone;
// 转换后的语音文件
this.outputFile = resolve(__dirname, "../assets/output.wav");
// 接口入参
this.params = {
host: "tts-api.xfyun.cn",
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 } = this.params;
const params = createAuthParams(this.params);
return `ws://${host}${path}?${params}`;
}
// 初始化
init() {
const reqUrl = this.generateWsUrl();
console.log(reqUrl);
this.ws = new WebSocket(reqUrl);
this.initWsEvent();
}
// 初始化websocket事件
initWsEvent() {
this.ws.on("open", this.onOpen.bind(this));
this.ws.on("error", this.onError);
this.ws.on("close", this.onClose);
this.ws.on("message", this.onMessage.bind(this));
}
/**
* websocket open事件,触发表示已成功建立连接
*/
onOpen() {
console.log("open");
this.onSend();
if (fs.existsSync(this.outputFile)) {
fs.unlinkSync(this.outputFile);
}
}
// 发送要转换的参数信息
onSend() {
const frame = {
// 填充common
common: {
app_id: this.params.appid,
},
// 填充business
business: {
aue: "raw",
auf: "audio/L16;rate=16000",
vcn: "xiaoyan",
tte: "UTF8",
},
// 填充data
data: {
text: Buffer.from(this.text).toString("base64"),
status: 2,
},
};
this.ws.send(JSON.stringify(frame));
}
// 保存转换后的语音结果
onSave(data) {
fs.writeFileSync(this.outputFile, data, { flag: "a" });
}
// websocket 消息接收 回调
onMessage(data, err) {
if (err) return;
const res = JSON.parse(data);
if (res.code !== 0) {
this.ws.close();
return;
}
// 接收消息结果并进行保存
const audio = res.data.audio;
const audioBuf = Buffer.from(audio, "base64");
this.onSave(audioBuf);
if (res.code == 0 && res.data.status == 2) {
this.ws.close();
this.onDone();
}
}
onClose() {
console.log("close");
}
onError(error) {
console.log(error);
}
}
module.exports = XunFeiTTS;
Демонстрация эффекта
Yuque - Вы можете увидеть эффект внизу статьи
Адрес источника
Адрес источника на гитхабеЕсли это поможет вам, пожалуйста, оставьте звезду~