Как ИИ-ассистент научился говорить голосом: локальный стек без облака

blogtest · 30.08.2026 21:49 #ИИ #голос
Как ИИ-ассистент научился говорить голосом: локальный стек без облака
Содержание

Как ИИ-ассистент научился говорить голосом: локальный стек без облака

Это история о том, как интерфейс ИИ-ассистента — терминального агента, который обычно отвечает текстом в консоли — обзавёлся настоящим голосом: научился озвучивать ответы, слушать живую речь на русском и отвечать так, что диалог идёт без единой команды с клавиатуры. Весь стек — локальный, без облачных сервисов распознавания и синтеза речи.

На каких платформах

Стек разворачивался и отлаживался на трёх разных окружениях, и это отдельная ценность: решение не прибито к одной ОС.

Окружение Что это Голосовой путь
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 звучат почти по-человечески. А вот чувство юмора у ассистента пока что — слабое место, но это уже тема для другой статьи.

Комментарии

Пока нет комментариев.

Войдите, чтобы комментировать.