Realtime-распознаватель речи на базе Vosk: управление микрофоном, детекция уровня голоса (whisper / normal / shout), опциональное шумоподавление, декораторный API для обработки текста, ключевых слов и быстрых команд.
- Установка
- Пример: простой голосовой помощник
- Управление (методы и рекомендации)
- Декораторы и
Filter(API событий) - Калибровка голоса (calibrate_voice.py)
- Конфиг для mode — как создать и применить
- Выбор микрофона вручную
- Режим шумоподавления (опционально)
- Отладка, советы и частые ошибки
- Структура проекта
- Лицензии
Установка через репозиторий
-
Склонируйте или скопируйте проект в папку:
git clone https://github.com/REYIL/VoiceTrigger.git cd VoiceTrigger -
Установите зависимости:
pip install -r requirements.txt
Установка через pip
pip install VoiceTriggerДополнительно
- Скачайте Vosk-модель (например,
model_small) с официального сайта Vosk. - Укажите путь к модели в параметре
model_pathпри использовании пакета.
Пример использования VoiceTrigger. В этом примере помощник «просыпается» по ключевому слову Алиса, слушает фразы, реагирует на быстрые команды (quick_words), и по длительной тишине возвращается в режим прослушивания ключевых слов.
import asyncio
import time
from pathlib import Path
from VoiceTrigger import (
VoiceTrigger,
Filter, TextContext,
ColorLogger, Mode
)
rms_thresholds = {
"whisper": -43.0,
"normal": -15.0,
"shout": 0.0
}
bot = VoiceTrigger(
model_path="model_small", # Путь к модели
keywords=["Алиса"], # Не обязательно, может брать автоматически с Filter
quick_words=["стоп", "назад", "вперед"], # Не обязательно, может брать автоматически с Filter
# calibration_path=Path("voice_calibration.json"),
# Путь указывать не обязательно
# Если не указывать будет пытаться брать из "voice_calibration.json", а если файла не будет то определение будет работать по системным параметрам
# rms_thresholds=rms_thresholds,
# Если калибровка работает плохо, можно сделать ручную настройку
device=None, # Устройство ввода, если не указывать выберет сам
logger=ColorLogger(level="debug") # Логгер
)
state = {"active_until": 0.0}
@bot.keyword(Filter("Алиса"))
async def on_alisa(ctx: TextContext):
bot.log.info(f"[KW] {ctx.match} mode={ctx.mode}")
bot.start_recognition_main()
bot.stop_recognition_keywords()
state["active_until"] = time.time() + 10.0
@bot.quick(Filter(["стоп", "пауза"]))
async def on_quick(ctx: TextContext):
bot.log.info(f"[QUICK] {ctx.match} mode={ctx.mode}")
if ctx.match and ctx.match.lower() == "стоп":
bot.stop_recognition_main()
bot.start_recognition_keywords()
state["active_until"] = 0.0
@bot.text()
async def on_all_text(ctx: TextContext):
if ctx.match is None and ctx.text:
bot.log.info(f"[TEXT] mode={ctx.mode} text='{ctx.text}'")
@bot.text(Filter(["привет", "здарова"], lv=10, mode=Mode.normal))
async def on_greeting(ctx: TextContext):
bot.log.info(f"[GREETING] {ctx.match} text='{ctx.text}' mode={ctx.mode}")
@bot.on_silence() # Возвращает время с последнего quick_words
async def handle_silence_main(sec: float):
now = time.time()
if 0 < state["active_until"] <= now and bot.active_main and sec >= 10.0:
bot.log.info(f"[Silence] {sec:.1f}s -> back to keywords")
bot.stop_recognition_main()
bot.start_recognition_keywords()
state["active_until"] = 0.0
# @bot.on_kw_silence() # Возвращает время с последнего keywords
# async def handle_kw_silence(sec: float):
# if sec >= 5.0:
# bot.log.debug(f"[KW Silence] {sec:.1f}s with no keywords")
if __name__ == "__main__":
devices = bot.list_input_devices() # Вывод всех аудио устройств
bot.log.debug(f"Available input devices: {devices}")
try:
asyncio.run(bot.run(initial_keywords_mode=True)) # Запуск с вначале включенным keywords_mode
except KeyboardInterrupt:
bot.log.info("Interrupted by user.")Основные методы:
start_recognition_main()— включить основной режим распознавания (continuous).stop_recognition_main()— выключить основной режим.start_recognition_keywords()— включить режим прослушивания ключевых слов.stop_recognition_keywords()— выключить режим ключевых слов.reload_model(new_model_path=None)— перезагрузить Vosk-модель (опционально указать новый путь).list_input_devices()— вернуть список доступных входных устройств (index, name, max_input_channels).set_input_device(device, restart_stream=False)— установить устройство ввода (индекс или имя).restart_stream=Trueпопытается перезапустить поток.
Рекомендации:
- Не рекомендуется включать одновременно
mainиkeywords. Эти режимы имеют разные цели —keywordsоптимизирован для обнаружения wake-word,main— для непрерывной речи. - Если нужна быстрая реакция на короткие команды, используйте
quick_wordsсовместно сmain— quick-обработчики работают параллельно с основным распознаванием и оптимизированы под короткие команды. keyword-режим хорош для «пробуждения» (wake word). Обычно вы запускаетеkeywordsпо умолчанию, а при обнаружении wake-word временно переключаетесь вmain.
Декораторы:
@bot.text(FILTER?)— обработчики общего текста (по умолчанию wildcard). Аргумент —TextContext.@bot.keyword(FILTER?)— обработчики ключевых слов; указанные фразы добавляются в списокkeywords.@bot.quick(FILTER?)— быстрые команды (короткие слова/фразы).@bot.on_silence()— обработчики тишины дляmain(параметр — количество секунд молчания).@bot.on_kw_silence()— обработчики тишины дляkeywords.
Filter:
Filter(phrases=None | "слово" | ["а","б"], lv=10, mode=Mode.normal|whisper|shout)phrases— список фраз; пустой список /None→ wildcard (обработчик принимает все тексты).lv— процент допуска ошибок для Levenshtein (число 0..100). Чем больше — тем сильнее допускаются отличия при сравнении.mode— (Mode.whisper,Mode.normal,Mode.shout) — если указан, обработчик вызовется только при совпадении голосового режима.
Контекст обработчика (TextContext):
text— распознанный текст (final/partial).mode— строка:"whisper" | "normal" | "shout".match— совпавшая фраза изFilterилиNone(для wildcard).timestamp— время события (epoch).
В проекте есть модуль VoiceCalibrator, она собирает статистику по трём уровням речи (quiet, normal, loud) и сохраняет voice_calibration.json. Этот файл используется VoiceTrigger для адаптивной настройки порогов RMS/HF и порога тишины.
Запуск калибровки:
from pathlib import Path
from VoiceTrigger import VoiceCalibrator
CALIBRATION_PATH = Path(__file__).parent / "voice_calibration.json"
VoiceCalibrator.calibrate(calibration_path=CALIBRATION_PATH) # Путь указывать не обязательноСкрипт попросит записать несколько фрагментов для каждого уровня и сохранит средние значения в voice_calibration.json.
Почему калибровка полезна:
- Подстраивает пороги под конкретный микрофон, акустику комнаты и расстояние до источника.
- Повышает корректность определения whisper/normal/shout.
Минусы калибровки:
- Требует аккуратного прохождения процедуры пользователем — если человек говорит слишком громко или тихо, результаты могут быть некорректными.
- Чувствительна к положению микрофона: смена расстояния или угла может сильно изменить RMS и HF, что исказит пороги.
- Автопороги могут оказаться слишком близко друг к другу, особенно если различие между whisper, normal и shout маленькое — классификация становится менее надёжной.
- Плохие условия записи (шум, эхо, фоновые источники) могут «засорить» калибровку.
- При многократной смене среды/оборудования нужна новая калибровка, иначе точность падает.
Можно задать пороги вручную через JSON-конфиг, либо использовать voice_calibration.json, полученный через calibrate_voice.py.
Пример mode_config.json:
{
"rms_thresholds": {
"whisper": -45.0,
"normal": -18.0,
"shout": -1.0
},
"hf_ratio_threshold": 1.5,
"silence_db": -46.0
}Применение конфигурации в коде:
import json
from pathlib import Path
from VoiceTrigger import VoiceTrigger
cfg = json.loads(Path("mode_config.json").read_text(encoding="utf-8"))
bot = VoiceTrigger(...)
bot.voice_detector.rms_thresholds = cfg.get("rms_thresholds", bot.voice_detector.rms_thresholds)
bot.voice_detector.hf_ratio_threshold = cfg.get("hf_ratio_threshold", bot.voice_detector.hf_ratio_threshold)
bot.voice_detector.silence_db = cfg.get("silence_db", bot.voice_detector.silence_db)Или положите результаты калибровки в voice_calibration.json — VoiceTrigger автоматически прочитает его при создании VoiceTrigger (если файл доступен).
А также можно использовать rms_thresholds
from VoiceTrigger import VoiceTrigger, ColorLogger
rms_thresholds = {
"whisper": -43.0,
"normal": -15.0,
"shout": 0.0
}
bot = VoiceTrigger(
model_path="model_small",
quick_words=["стоп", "назад", "вперед", "какая погода"],
logger=ColorLogger(level="debug"),
rms_thresholds=rms_thresholds
)Список устройств:
devices = VoiceTrigger.list_input_devices()
# или через экземпляр:
devices = bot.list_input_devices()Установка устройства:
- При инициализации:
bot = VoiceTrigger(..., device=2) # индекс
# или
bot = VoiceTrigger(..., device="USB Microphone") # имя устройства- Во время работы:
bot.set_input_device(2, restart_stream=True)restart_stream=True попытается перезапустить поток (может потребоваться освобождение устройства системой).
Можно включить встроенное шумоподавление для микрофона. Для этого необходимо:
-
Установить зависимости:
pip install noisereduce scipy
-
Включить режим в коде:
bot = VoiceTrigger(..., noise_reduction=True)
Когда использовать:
- если в помещении много фонового шума (ПК-вентиляторы, улица, кондиционер),
- при записи на встроенные микрофоны ноутбука,
- если нужно повысить точность распознавания.
- Включите подробный логгер:
logger = ColorLogger(level="debug")
bot = VoiceTrigger(..., logger=logger)-
Если модель не загружается — проверьте
model_pathи файлы модели. -
Если нет звука или пустые результаты — проверьте
sounddevice.query_devices()и системные права доступа к микрофону. -
Если плохое распознавание:
- проверьте sample rate (обычно 16000),
- расположение микрофона,
- при необходимости запустите
calibrate_voice.py, - попробуйте включить
noise_reduction(если установленnoisereduce).
-
Для коротких команд используйте
quick_words(работают быстрее и подходят для single-word commands).
.
├── model_small # vosk
├── voicetrigger
│ ├── core
│ │ ├── asmanager.py
│ │ ├── decorators.py
│ │ ├── speechr.py
│ │ └── vldetector.py
│ ├── services
│ │ └── calibration.py
│ ├── utils
│ │ ├── filter.py
│ │ ├── levenshtein.py
│ │ └── logger.py
│ └── __init__.py
├── main.py
└── requirements.txt
Основной код проекта распространяется под лицензией MIT — вы можете свободно использовать, изменять и распространять его.
Проект также использует сторонние библиотеки:
- colorlog — лицензия MIT
- noisereduce — лицензия MIT
- numpy — лицензия BSD 3-Clause
- scipy — лицензия BSD 3-Clause
- sounddevice — лицензия MIT
- vosk — лицензия Apache License 2.0
Все перечисленные библиотеки используются в соответствии с условиями их лицензий.