引言
今年上半年,我曾在基于RK3566的嵌入式開發板上本地部署并運行大模型,當時僅能在終端界面使用文字進行交互,而我的進一步目標是實現本地的大模型語音交互。但限于我手里的開發板內存不足,無法加載本地的語音識別模型,同時經費有限,只能暫時擱置。下半年,遇到電源網和得捷舉辦DIY活動,提供600元的經費報銷,使得該項目得以繼續。
本文將介紹在嵌入式終端上,基于本地大模型實現的多語言離線語音聊天機器人。其中,語音識別、大模型推理、文字轉語音(TTS)都是在嵌入式終端基于本地模型實現的,無需接入互聯網環境。
特點
-基于嵌入式終端實現
-無需聯網,完全離線運行
-多語言支持(中、英)
-代碼開源,模型易部署及更換
硬件環境
-Raspberry Pi 5(quad-core Arm Cortex A76 processor @ 2.4GHz, 8GB RAM)
-WM8960 音頻模塊
-2寸LCD主屏+ 0.96寸OLED雙副屏
軟件及模型
-Python: 3.11.2
-推理框架:llama-cpp-python
-語音識別:SenseVoice大模型
-推理模型:千問大模型
-文本轉語音:Piper TTS
整體框架
(按要求使用Scheme-it繪制)
環境準備
安裝llama推理框架
直接用pip安裝:
pip install llama-cpp-python
安裝SenseVoice依賴
在SenseVoice的Github倉庫,提供了requirements.txt。如果是直接使用我提供的源碼,無需拉取SenseVoice倉庫,requirements.txt存放于\models\SenseVoiceSmall
目錄。使用pip安裝必要的依賴:
pip install -r requirements.txt
安裝Piper TTS
Piper TTS是我目前找到的較優離線TTS,語音接近人聲,加載速度快,完全離線運行。它無需特別安裝,只需要下載編譯好的二進制可執行文件,即可使用,我提供的源碼已經直接包含,存放于\piper
目錄。
特別說明:上述安裝截圖中pip安裝含有
--break-system-packages
選項,這是我的系統Python結構的原因,在其它系統Python環境下,可能是不需要的。
安裝音頻及顯示驅動
本文使用的音頻及顯示模塊,均來自微雪電子,可直接參考對應模塊的教程,進行驅動安裝即可,如有問題,可聯系其技術支持。
-WM8960音頻模塊驅動安裝
-LCD+OLED三聯屏顯示驅動安裝
獲取本項目源碼
-獲取項目源碼:
本項目完整源碼,通過gitee開源,可通過git拉取
-放置模型文件:
受限于git倉庫對單個文件的大小的限制,兩個較大的模型文件單獨提供網盤下載
注:源碼網址見文末
代碼說明
線程結構
本項目代碼主要由多個線程組成,包含按鍵線程、錄音線程、語音識別線程、模型推理線程、文字轉語音線程、顯示線程等,各線程通過事件進行觸發并流轉運作。
主要線程代碼
Key線程
Device.pin_factory = LGPIOFactory()
# key init
key2 = Button(17)
def key2_pressed():
start_record_event.set()
stop_tts_event.set()
show_record_event.set()
def key2_released():
stop_record_event.set()
model_doing_event.set()
stop_tts_event.clear()
show_record_event.clear()
# Bind key press event
key2.when_pressed = key2_pressed
key2.when_released = key2_released
該線程主要監聽按鍵的按下和釋放事件,以觸發語音錄制及識別等相關動作。
錄音線程
def recording_thread():
while True:
start_record_event.wait()
start_record_event.clear()
device = "default"
wavfile = wave.open(f"{current_dir}/record.wav", "wb")
mic = alsaaudio.PCM(
alsaaudio.PCM_CAPTURE,
alsaaudio.PCM_NONBLOCK,
channels=1,
rate=44100,
format=alsaaudio.PCM_FORMAT_S16_LE,
periodsize=160,
device=device,
)
wavfile.setnchannels(1)
wavfile.setsampwidth(2) # PCM_FORMAT_S16_LE
wavfile.setframerate(44100)
print("Start speaking...")
time_start = datetime.now()
while True:
if stop_record_event.is_set():
stop_record_event.clear()
time_stop = datetime.now()
print("Stop speaking...")
wavfile.close()
if time_stop.timestamp() - time_start.timestamp() >= 1:
trig_sensevoice_event.set()
else:
print("The speaking time is too short")
model_doing_event.clear()
break
# Read data from device
l, data = mic.read()
if l:
wavfile.writeframes(data)
time.sleep(0.001)
錄音線程,在KEY按下后被觸發執行循環錄制,KEY釋放后退出錄制。此處還做了簡單的錄音時長的判斷,因為當錄音時長過短時,后續的語音識別可能會報錯。
語音識別線程
def sensevoice_thread():
from model import SenseVoiceSmall
from funasr.utils.postprocess_utils import rich_transcription_postprocess
model_dir = f"{current_dir}/models/SenseVoiceSmall"
m, kwargs = SenseVoiceSmall.from_pretrained(model=model_dir, device="cuda:0")
m.eval()
senvc_load_done.set()
print("Load sensevoice model done")
while True:
trig_sensevoice_event.wait()
trig_sensevoice_event.clear()
res = m.inference(
data_in=f"{current_dir}/record.wav",
language="auto", # "zh", "en", "yue", "ja", "ko", "nospeech"
use_itn=False,
ban_emo_unk=False,
**kwargs,
)
text = rich_transcription_postprocess(res[0][0]["text"])
ask_text_q.put(text)
trig_llama_event.set()
在錄音線程正常執行完成后,觸發執行語音識別線程。SenseVoice語音識別使用較為簡單,可自動識別多種語言,生成的文本直接放置到消息隊列中,供下一步模型推理使用。模塊的初始import是較費時間的,為了不影響程序的整體加載時間,所以關于SenseVoice模塊的import處理也放置在了線程中,而不是統一放在文件的開頭
------無奈的結束分割線(受論壇帖子字數限制,后續見下一帖子)-----