Как ИИ-ассистент научился говорить голосом: локальный стек без облака
Содержание
Как ИИ-ассистент научился говорить голосом: локальный стек без облака
Это история о том, как интерфейс ИИ-ассистента — терминального агента, который обычно отвечает текстом в консоли — обзавёлся настоящим голосом: научился озвучивать ответы, слушать живую речь на русском и отвечать так, что диалог идёт без единой команды с клавиатуры. Весь стек — локальный, без облачных сервисов распознавания и синтеза речи.
На каких платформах
Стек разворачивался и отлаживался на трёх разных окружениях, и это отдельная ценность: решение не прибито к одной ОС.
| Окружение | Что это | Голосовой путь |
|---|---|---|
| Termux на Android | планшет и складной телефон | TTS через Termux:API, STT — локальный Vosk/whisper.cpp |
| WSL/Ubuntu | Windows-ноутбук с Linux-подсистемой | TTS — Piper (нейроголос), STT — whisper.cpp |
| обычный терминал | любой шелл | тот же конвейер скриптов, что и выше |
Ниже про конкретные программы и сборку — как в каждом окружении.
Синтез речи (TTS): сказать
Чтобы ассистент «говорил», нужен синтез речи. Использовали два механизма, в зависимости от платформы.
На Android (Termux) — Termux:API
termux-tts-speak из пакета Termux:API — мост к системному TTS Android (это Google или вендорский движок). Передаёшь текст, а язык определяется сам: русский и английский озвучиваются нормально. Это самый простой путь «ответить голосом»: одна команда, текст — аргументом.
Грабли были на старте: синтезатор «висит» или молчит, пока приложению Termux:API не выдашь разрешения (микрофон — если используешь и распознавание, а также разрешения в настройках Android; на отдельных оболочках — ещё и Google-приложениям). После выдачи прав — работает.
На WSL — Piper
Piper — локальный нейросетевой синтез: модели лёгкие (десятки мегабайт), звучат в разы естественнее системного TTS Android. Для русского взяли модель ru_RU-ruslan-medium. Piper отдаёт raw-аудиопоток, который льётся в систему через paplay (PulseAudio). Голос — мужской, читает ровно, с интонацией. На ноутбуке синтез фразы занимает пару секунд — для диалога терпимо. Резервные варианты, которые не понадобились: облачный TTS и совсем «роботный» espeak.
Распознавание речи (STT): услышать
С распознаванием было сложнее: системное распознавание Termux:API зашито на английский (en-US) в коде приложения и русскую речь не понимает принципиально. Поэтому — локальные офлайн-движки, их два, и оба переключаемы на лету.
Vosk — быстрый и лёгкий
Vosk — офлайн-распознавание, ставится из pip как Python-модуль + качается модель. Для русского взяли vosk-model-small-ru (~47 МБ), для английского — small-en (~41 МБ). Важная деталь сборки в Termux: Vosk тянет glibc-сборки, поэтому Python в Termux ставится из glibc-репозитория, а vosk — именно в него (pip glibc-python). Под обычным bionic-Python wheel не встаёт.
Известные грабли: большие модели Vosk (на 1.5+ ГБ) лучше не использовать — они грузятся в память целиком и роняют систему даже при свободных гигабайтах. Хватает small.
Whisper.cpp — точнее и с пунктуацией
Whisper.cpp — это C++-порт OpenAI Whisper под железо попроще: работает без GPU и без облака, на CPU быстрее реального времени. Это второй движок распознавания, он заметно точнее по русскому и проставляет пунктуацию.
Сборка из исходников (CMake):
git clone <репозиторий whisper.cpp>
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --target whisper-cli whisper-server
В официальном репозитории есть скрипт download-ggml-model.sh, который качает готовые модели; для русского взяли base (~141 МБ) и её квантованную версию base-q5_1 (~57 МБ).
Квантование стало отдельным открытием
Квантованная модель base-q5_1 распознаёт не хуже обычной, при этом вдвое меньше по памяти и заметно быстрее. Более того, в эксперименте она «пропустила» неразборчивый фоновый шум, который полная модель отчаянно пыталась транскрибировать в бессмыслицу — работает как простое подавление постороннего голоса.
Чтобы модель не загружалась заново на каждую фразу, поднимается whisper-server — HTTP-демон: модель загружена в память один раз, скрипт просто шлёт WAV по POST и получает текст в JSON. На практике это возвращает распознавание примерно за пару секунд.
Детектор тишины (VAD): понять, когда фраза закончена
Важная часть диалога — понять, что собеседник закончил говорить. Для этого используется VAD (voice activity detection) — детектор, режущий запись по тишине.
На Android это оказалось нетривиально: системный микрофонный рекордер Termux пишет в m4a с метаданными в конце — файл не читается до завершения записи. Рабочий путь — потоковый захват через PulseAudio (parec, источник с микрофона) + маленький Python-скрипт, который считает RMS громкости потоком и ставит флаг, когда после последнего звука тишина держится ~2 секунды. После флага запись конвертируется в WAV через ffmpeg и уходит на распознавание.
На WSL микрофон пробрасывается средой (источник RDPSource), а сам VAD — тот же Python-детектор поверх parec.
Порог активируется не «с места»: тишина в начале не считается, а сам порог подстраивается под фоновый шум. Короткие слова в начале фразы тоже не теряются (чувствительность откалибрована — распознавание запускается по 0.5 секунды непрерывного звука).
Одна команда «сказал + слушаю»
Главное в диалоге — не дать собеседнику уйти в паузу между твоим ответом и началом прослушивания. Поэтому сделан скрипт say-listen.sh — одной командой: озвучить текст и сразу же начать слушать, без зазора.
Этапы (вывод в stderr, чтобы человек видел, что происходит):
слушаю (макс 30с, стоп — тишина 2.0с). Говори...
тишина поймана — речь закончена, отдаю звук
аудио записано, распознаю...
распознавание (whisper, ru)...
готово: <распознанный текст>
Плюс звуковые сигналы, чтобы человек без экрана понимал состояние: короткий мягкий восходящий тон после ответа (начинаю слушать) и нисходящий — когда тишина поймана (перестал слушать). Мелкие wav-файлы, проигрываются через paplay.
Переключение движков
Диспетчер распознавания recognize.sh выбирает движок (Vosk или whisper) по конфиг-файлу, а stt-mode.sh его переключает — даже прямо в диалоге, голосом: «переключи на виспер». Следующий обмен уже распознаётся новым движком. TTS-часть при этом не меняется — работает как раньше.
Почему всё локально
Всё распознавание и синтез — офлайн. Никаких облачных API распознавания речи: данные не уходят, работает без интернета, и нет лимитов по объёму. Единственное исключение — системный TTS на Android (он вендорский), но для него тоже можно подставить локальный (Piper работает и на ARM, ставится из pip).
Впечатления
Голосовой интерфейс меняет ощущение от ИИ-агента. Текст — это работа с инструментом: ввёл, получил, прочитал. Голос — это разговор: можно проверять почту, спрашивать про планы и получать ответы, не отрываясь от дел. Первый живой диалог на планшете («Привет, Гоша! Это проверка распознавания русской речи») распознался дословно — момент, когда понимаешь, что всё получилось.
Что раздражает честно: распознавание чувствительно к шуму. Если рядом играет радио или стиральная машина, длинная фраза может превратиться в кашу, а однажды ассистент всерьёз принялся транскрибировать голос диджея из фонового радио. Лучше всего распознаются короткие чёткие фразы в тишине; подсказка на будущее — микрофон наушников (ближе ко рту) даёт заметно чище.
Синтез речи уже «на уровне»: нейроголоса Piper звучат почти по-человечески. А вот чувство юмора у ассистента пока что — слабое место, но это уже тема для другой статьи.
Комментарии
Пока нет комментариев.
Войдите, чтобы комментировать.