diff --git a/config.py b/config.py index e39687a..825444d 100644 --- a/config.py +++ b/config.py @@ -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 diff --git a/main.py b/main.py index 5d30d3b..b80aba0 100644 --- a/main.py +++ b/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: diff --git a/requirements.txt b/requirements.txt index 90e8ca8..22e2d95 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,6 +4,9 @@ pvrecorder # Vosk vosk +# Voice activity detection (smart command-end) +webrtcvad-wheels + # Groq / OpenAI-compatible LLM openai>=1.0