高端玩家!樹莓派 + Node.js 實現語音機器人 🤖

2020-11-06 流浪的思維

volute 是什麼?


volute(蝸殼)是一個使用 Raspberry Pi+Node.js 製作的語音機器人.


什麼是樹莓派?




樹莓派(英語:Raspberry Pi)是基於 Linux 的單片機電腦,由英國樹莓派基金會開發,目的是以低價硬體及自由軟體促進學校的基本計算機科學教育。


樹莓派每一代均使用博通(Broadcom)出產的 ARM 架構處理器,如今生產的機型內存在 2GB 和 8GB 之間,主要使用 SD 卡或者 TF 卡作為存儲媒體,配備 USB 接口、HDMI 的視頻輸出(支持聲音輸出)和 RCA 端子輸出,內置 Ethernet/WLAN/Bluetooth 網絡連結的方式(依據型號決定),並且可使用多種作業系統。產品線型號分為 A 型、B 型、Zero 型和 ComputeModule 計算卡。


簡單的說,這是一臺可以放到口袋裡的電腦!!


什麼是 Node.js?



原先 Javascript 只能依賴瀏覽器環境執行.Node.js 的誕生,讓我們可以在伺服器端使用 Javascript.Node.js 是一個能執行 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 一樣開機就能用,想要順利體驗樹莓派,還得一步一步來~


燒錄作業系統


樹莓派沒有硬碟結構,僅有一個 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/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


語音助手需要像市面上的設備一樣,需要喚醒。如果沒有喚醒步驟,一直做監聽的話,對存儲資源和網絡連接的需求是非常大的。


Snowboy 是一款高度可定製的喚醒詞檢測引擎(Hotwords Detection Library),可以用於實時嵌入式系統,通過訓練熱詞之後,可以離線運行,並且 功耗很低。當前,它可以運行在 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;


語音聽寫 科大訊飛 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; // 返回結果,解析後的消息文字 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;


聊天機器人 圖靈機器人 API


圖靈機器人 API V2.0 是基於圖靈機器人平臺語義理解、深度學習等核心技術,為廣大開發者和企業提供的在線服務和開發接口。


目前 API 接口可調用聊天對話、語料庫、技能三大模塊的語料:


聊天對話是指平臺免費提供的近 10 億條公有對話語料,滿足用戶對話娛樂需求;


語料庫是指用戶在平臺上傳的私有語料,僅供個人查看使用,幫助用戶最便捷的搭建專業領域次的語料。


技能服務是指平臺打包的 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;


語音合成 科大訊飛 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; // 要轉換的文字 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;


效果演示

對話.m4a來自前端試煉00:0000:11

深圳天氣.m4a來自前端試煉00:0000:16

順口溜.m4a來自前端試煉00:0000:20

連結

https://mp.weixin.qq.com/s/x96yNp-RylWvTe1zcE2-IA

相關焦點

  • 使用樹莓派打造家庭監控系統
    要做到這一點,我們將使用 B +型號的樹莓派開發板和官方的樹莓派相機模塊。此外,我們還將使用溫溼度傳感器進行一些測量工作。開始項目之前,你需要的第一個東西就是樹莓派 B +開發板。它具有很強大的功能(如4個USB埠),當然你也可以使用較舊版本的樹莓派。您將需要使用官方的 Raspberry Pi 相機模塊來拍攝照片。還將使用 DHT11(或DHT22)傳感器來測量家中的溫度和溼度。由於我們將遠程訪問 Rapsberry Pi,因此你將需要一個USB接口的無線網卡。
  • 學 Rust,免費拿樹莓派
    如果你對 WebAssembly 有興趣,歡迎至文末添加小助手微信,加入 WebAssembly 中文交流群創建並發布一個高性能 Node.js 應用程式。開始在個人項目或公司項目中使用」當紅炸子雞「 Rust 吧!
  • nodejs mqtt 智能售貨機系統物聯網控制系統源碼分享
    主要涉及到的語言和庫有c,c++,js,nodejs,vue.js,thinkphp。 整套系統主要作用是打通網站後臺,網站前端,手機前端,單片機和微型電腦之間的通信和系統的構建。這裡是f1c100s晶片系列rt-thread系統的實現,gpio,pwm,uart,otg等等基本功能都已經實現。
  • 3D 列印的樹莓派蜘蛛機器人
    這個四足蜘蛛機器人以樹莓派作為「大腦」,身軀和四肢由 3D 列印。無需定製電路板,初學者即可完成組裝。不僅如此,教程還包含一段將近一個小時的組裝視頻。這對於想了解樹莓派、Python 編程的朋友來說是非常不錯的上手項目。
  • 如此魔改樹莓派?工程師的腦洞不服不行!
    ●板載雙頻802.11 b/g/n/ac Wi-Fi、藍牙5.0(BLE)●一個擴展的40針GPIO接頭,MIPI攝像頭和顯示器埠,以及真正的千兆乙太網隨著板載 WiFi及藍牙5的支持和新的 64 位CPU,樹莓派4B能處理的事情也越來越多,甚至可以實現輕辦公,這讓DIY圈的愛好者們躍躍欲試,讓我們來看看他們都玩出什麼花樣來吧!
  • [圖+視頻]用Raspberry Pi(樹莓派)打造的R2-D2星戰機器人
    計算機科學博士生項凌翔(音譯),最近又挖掘出了樹莓派
  • 在樹莓派上實現人臉識別
    本教程將幫助你建立一個可以訓練 HARASCALDES 模型的樹莓派,該模型可用於檢測已識別的/未識別過的人,使用監控攝像頭進行實時監控,並利用物聯網 JumpWay 來發送傳感和警告消息,進而允許你的設備利用其他物聯網 JumpWay 網與其他設備進行通信。
  • 用樹莓派做蜘蛛機器人,還是3D列印的!
    這個四足蜘蛛機器人以樹莓派作為「大腦」,身軀和四肢由 3D 列印。無需定製電路板,初學者即可完成組裝。 不僅如此,教程還包含一段將近一個小時的組裝視頻。這對於想了解樹莓派、Python 編程的朋友來說是非常不錯的上手項目。
  • 全開源PicoRio對標銷量超3000萬件的樹莓派
    樹莓派推出的初衷是用於教育,但其目前已經廣泛應用於各種產品中。比如新冠肺炎疫情期間,就有公司大量採購樹莓派用於生產呼吸機。這體現了基於Arm晶片的樹莓派有一個非常良好的生態,其低成本的硬體、活躍的社區和詳細的文檔讓開發者可以在短時間內開發出想要的產品。「樹莓派的成功說明這樣的模式對構建社區非常有幫助,值得我們借鑑。」
  • 4步實現樹莓派人臉識別、拍照與推送
    由於無法放連結,可關注以下獲取原文:大部分童鞋的樹莓派是不是一直在吃灰呢?一直閒置著,倒不如用它做一個簡易監控,如果檢測到人臉後,就拍照上傳到指定地方,或發消息提醒。本內容來源於B站「基於樹莓派的魔鏡」,感興趣的童鞋可以觀看演示視頻和教程。
  • 全開源PicoRio對標銷量超3000萬件的樹莓派
    但許多人可能並不了解2012年面世的基於Arm晶片的微型電腦樹莓派(Raspberry Pi)對Arm生態成功的重要性。樹莓派是為學習計算機編程教育而設計,2019年12月全球銷量超過了3000萬件,對Arm生態的成熟、甚至嵌入式系統的發展都意義重大。
  • 全球樹莓派DIY也瘋狂:超級電腦、手機與平板動手做
    Pi 基金會開發的 Raspberry Pi 微型電腦板,也就是我們常稱的「樹莓派」在推出之後火爆全球,不管是開發者,還是一些 DIY 玩家都在沉迷於此,鑑此,本文網羅全球DIY大牛們最具創意的作品,以饗電子發燒友網讀者。
  • 啥都學點之使用nvm安裝Node.js並實現Node.js多版本管理
    這種情況下,對於多個版本的Node.js的切換將是一件非常麻煩的事情。這樣才能方便的管理和切換Node.js版本呢,NVM就是一個很好的解決方案,安裝NVM後,可以方便的在一臺設備上進行多個Node.js版本的切換,滿足不同項目的開發和運營。
  • centos7編程實踐:安裝nodejs
    Node.js是一個javascript運行環境。它讓javascript可以開發後端程序,實現幾乎其他後端語言實現的所有功能,可以與PHP、Java、Python、.NET、Ruby等後端語言平起平坐。
  • 如何安裝Node.js
    如何安裝Node.js 本文分別介紹在Mac, Ubuntu,Centos以及Windows下安裝Node.js.安裝git3 .運行下面的命令行編譯node.jsgit clone git://github.com/joyent/node.git  cd node  .
  • 教你動手做:3D列印的樹莓派蜘蛛機器人
    MAKER: Morrisl4/譯:趣無盡這個四足蜘蛛機器人以樹莓派作為「大腦」,身軀和四肢由 3D 列印。無需定製電路板,初學者即可完成組裝。推薦使用 2GB 內存以上的樹莓派 + Raspberry Pi OS 桌面版。
  • 在樹莓派上玩 Steam 遊戲的方法
    更棒的是 Steam Link (beta) 也支持樹莓派!Steam Link 的原理是利用 WiFi 網絡,把電腦的畫面串流到樹莓派上,而樹莓派上已連接的遊戲手柄則可用於操作遊戲。你可在其他地方用樹莓派暢玩電腦內的 Steam 遊戲。換句話說,玩家必須事先在 Steam 平臺購買好遊戲,並將之安裝到電腦,樹莓派只用作第二個畫面顯示用途及操作用途,遊戲不會佔據樹莓派的儲存空間。
  • 歡迎來到AI的世界:從樹莓派,Arduino 到 HEXA | 了解機器人開發必看
    啥是樹莓派?樹莓派是將電腦集成到了一塊電路板上的微型電腦。對於偏編程開發的技術人員來說,樹莓派是一個非常低成本的開發平臺。同時,它的編程環境是標準的Linux編程環境,對於有經驗的程式設計師來說上手非常容易。
  • 萬能的樹莓派:各種奇葩DIY
    它由樹莓派控制,不僅能接收遙控指令,還能用攝像頭無線傳輸車子看到的影像,也就是實現了陸上無人機的基本功能,如果給它裝上小手槍的話……大家還記得COD6、COD8中那些能發射飛彈、轉管機槍的空中/陸上無人機麼? 看起來不牛X但實際很強:超級計算機