feat: add webrtcvad-based smart command-end detection
Replace the fixed 10s post-wake-word listen window with a VAD-driven loop. Once at least 500ms of speech have been seen, 1.2s of silence closes the window. Hard cap at 15s as a safety net. Tunable in config.py via VAD_AGGRESSIVENESS, COMMAND_END_SILENCE_MS, COMMAND_MIN_SPEECH_MS, COMMAND_MIN_LISTEN_MS, COMMAND_MAX_LISTEN_MS.
This commit is contained in:
parent
277921ce14
commit
794d923bf9
3 changed files with 45 additions and 6 deletions
|
|
@ -26,3 +26,11 @@ CHROME_PATH = 'C:/Program Files (x86)/Google/Chrome/Application/chrome.exe %s'
|
|||
GROQ_TOKEN = os.getenv('GROQ_TOKEN')
|
||||
GROQ_BASE_URL = "https://api.groq.com/openai/v1"
|
||||
GROQ_MODEL = "llama-3.3-70b-versatile"
|
||||
|
||||
# VAD (webrtcvad) — определяет конец команды по тишине вместо фиксированных 10 сек.
|
||||
# 0..3, выше — агрессивнее режет шум (но и обрезает речь).
|
||||
VAD_AGGRESSIVENESS = 2
|
||||
COMMAND_END_SILENCE_MS = 1200
|
||||
COMMAND_MIN_SPEECH_MS = 500
|
||||
COMMAND_MIN_LISTEN_MS = 1000
|
||||
COMMAND_MAX_LISTEN_MS = 15000
|
||||
|
|
|
|||
40
main.py
40
main.py
|
|
@ -13,6 +13,7 @@ import openai
|
|||
from openai import OpenAI
|
||||
import simpleaudio as sa
|
||||
import vosk
|
||||
import webrtcvad
|
||||
import yaml
|
||||
from comtypes import CLSCTX_ALL
|
||||
from fuzzywuzzy import fuzz
|
||||
|
|
@ -54,6 +55,10 @@ wake_rec = vosk.KaldiRecognizer(model, samplerate, wake_grammar)
|
|||
kaldi_rec = vosk.KaldiRecognizer(model, samplerate)
|
||||
q = queue.Queue()
|
||||
|
||||
VAD_FRAME_MS = 30
|
||||
VAD_FRAME_BYTES = int(samplerate * VAD_FRAME_MS / 1000) * 2
|
||||
vad = webrtcvad.Vad(config.VAD_AGGRESSIVENESS)
|
||||
|
||||
|
||||
def gpt_answer():
|
||||
global message_log
|
||||
|
|
@ -301,20 +306,43 @@ while True:
|
|||
|
||||
play("greet", True)
|
||||
print("Yes, sir.")
|
||||
# reset the command recognizer so stale audio doesn't bleed into it
|
||||
kaldi_rec.Reset()
|
||||
ltc = time.time()
|
||||
listen_started = time.time()
|
||||
speech_ms = 0
|
||||
silence_ms = 0
|
||||
vad_buf = b""
|
||||
finalize = False
|
||||
|
||||
while True:
|
||||
elapsed_ms = (time.time() - listen_started) * 1000
|
||||
if elapsed_ms >= config.COMMAND_MAX_LISTEN_MS:
|
||||
break
|
||||
|
||||
while time.time() - ltc <= 10:
|
||||
pcm = recorder.read()
|
||||
sp = struct.pack("h" * len(pcm), *pcm)
|
||||
kaldi_rec.AcceptWaveform(sp)
|
||||
|
||||
if kaldi_rec.AcceptWaveform(sp):
|
||||
if va_respond(json.loads(kaldi_rec.Result())["text"]):
|
||||
ltc = time.time()
|
||||
vad_buf += sp
|
||||
while len(vad_buf) >= VAD_FRAME_BYTES:
|
||||
frame = vad_buf[:VAD_FRAME_BYTES]
|
||||
vad_buf = vad_buf[VAD_FRAME_BYTES:]
|
||||
if vad.is_speech(frame, samplerate):
|
||||
speech_ms += VAD_FRAME_MS
|
||||
silence_ms = 0
|
||||
else:
|
||||
silence_ms += VAD_FRAME_MS
|
||||
|
||||
if elapsed_ms < config.COMMAND_MIN_LISTEN_MS:
|
||||
continue
|
||||
if speech_ms >= config.COMMAND_MIN_SPEECH_MS and silence_ms >= config.COMMAND_END_SILENCE_MS:
|
||||
finalize = True
|
||||
break
|
||||
|
||||
if finalize or speech_ms > 0:
|
||||
text = json.loads(kaldi_rec.FinalResult()).get("text", "")
|
||||
if text:
|
||||
va_respond(text)
|
||||
|
||||
wake_rec.Reset()
|
||||
|
||||
except Exception as err:
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ pvrecorder
|
|||
# Vosk
|
||||
vosk
|
||||
|
||||
# Voice activity detection (smart command-end)
|
||||
webrtcvad-wheels
|
||||
|
||||
# Groq / OpenAI-compatible LLM
|
||||
openai>=1.0
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue