Обычный Whisper принимает готовый файл и отдаёт текст через несколько секунд или минут — этого достаточно для расшифровки записи звонка, но не для субтитров на лету или голосового ассистента. Потоковая транскрипция обрабатывает звук кусками по мере поступления и показывает текст с задержкой в доли секунды. Разберём, как собрать такой конвейер на своём VDS.
Чем потоковая транскрипция отличается от пакетной
Базовая установка Whisper, описанная в статье про транскрипцию аудио и видео на VPS, работает с целым файлом: модель видит весь контекст сразу и выдаёт наиболее точный результат. Поток же приходит без конца — микрофон, звонок, эфир — и ждать полную запись нельзя.
Решение — резать поток на короткие сегменты по 2-5 секунд, распознавать каждый почти независимо и склеивать результат. Плата за скорость — модель видит меньше контекста, поэтому точность на границах сегментов немного ниже, чем при пакетной обработке.
Нарезка аудио и определение речи (VAD)
Резать поток строго по таймеру плохо: слово может оказаться разрезанным пополам между двумя чанками. Voice Activity Detection (VAD) находит паузы в речи и режет именно по ним. Библиотека silero-vad определяет границы фраз за миллисекунды и не требует GPU.
pip install silero-vad faster-whisper sounddevice numpy
python3 -c "import torch; model, utils = torch.hub.load('snakers4/silero-vad', 'silero_vad')"
Типичная схема: буфер накапливает аудио, VAD отмечает конец фразы, накопленный кусок сразу уходит в модель распознавания, буфер очищается.
faster-whisper в потоковом режиме
Для потоковой обработки лучше подходит faster-whisper — реализация на CTranslate2, которая распознаёт короткие сегменты в 2-4 раза быстрее оригинального PyTorch-кода Whisper. На CPU-сервере с 8 ядрами модель size small обрабатывает 3-секундный чанк примерно за 0.5-0.8 секунды — этого достаточно для субтитров без заметной задержки.
from faster_whisper import WhisperModel
model = WhisperModel("small", device="cpu", compute_type="int8")
segments, info = model.transcribe("chunk.wav", language="ru", beam_size=1)
for seg in segments:
print(seg.start, seg.end, seg.text)
beam_size=1 и модель размера small или base — обязательный компромисс для потока: полноразмерный large-v3 с большим beam search даёт задержку в несколько секунд на чанк, что рушит идею реального времени.
Сервер на WebSocket для приёма звука
Клиент — браузер или приложение — шлёт аудио кусками через WebSocket, сервер копит буфер, режет по VAD и отдаёт текст обратно тем же соединением. Минимальный сервер на websockets и asyncio:
import asyncio, websockets
async def handle(ws):
buffer = bytearray()
async for chunk in ws:
buffer.extend(chunk)
if len(buffer) > 48000 * 2 * 3: # ~3 секунды звука 16-бит
text = transcribe_chunk(bytes(buffer))
await ws.send(text)
buffer.clear()
asyncio.run(websockets.serve(handle, "0.0.0.0", 8765).__aenter__())
В проде вместо ручного накопления по размеру буфера используйте VAD из предыдущего раздела — он режет по паузам речи, а не по фиксированному числу байт.
Задержка против точности: что выбрать
| Размер чанка | Задержка | Точность | Когда использовать |
|---|---|---|---|
| 1-2 сек | минимальная | ниже, рвутся слова | живые субтитры, ассистент |
| 3-5 сек | умеренная | хорошая | звонки, совещания |
| 8-10 сек | заметная | высокая | лекции, подкасты |
| весь файл | после записи | максимальная | архивная расшифровка |
Для большинства задач субтитров в реальном времени разумный компромисс — чанк 3 секунды с моделью small на CPU или medium на GPU.
Куда встроить готовый транскрипт
Текст в реальном времени пригождается в трёх сценариях: субтитры на трансляции, голосовой ввод в чат-бота и поиск по звонкам. Если транскрипт нужен как вход для LLM-агента — например, чтобы бот отвечал голосом на вопрос из речи — удобно отдавать результат в OpenAI-совместимый эндпоинт через LocalAI, объединив распознавание речи и генерацию ответа в одном API. Для контроля задержки в проде полезно подключить мониторинг AI-сервисов и отслеживать время обработки каждого чанка отдельной метрикой.
- Живые субтитры — WebSocket-сервер плюс VAD плюс модель
small/base. - Голосовой ввод для бота — тот же конвейер, но с накоплением до конца фразы перед отправкой в LLM.
- Поиск по звонкам — можно вернуться к пакетной обработке: задержка не критична, важна точность.
Чек-лист запуска потоковой транскрипции
Перед тем как выкатывать поток в прод, проверьте четыре момента: какую задержку реально допускает сценарий, хватает ли CPU или нужен GPU, обрезает ли VAD слова на границах и что происходит при обрыве соединения — буфер не должен теряться молча.
- Определите допустимую задержку сценария: 1-2 сек для ассистента, 5-10 сек для субтитров лекции.
- Выберите размер модели под ресурс:
base/smallна CPU,medium+ на GPU. - Настройте VAD, а не резку по таймеру — так меньше рваных слов на стыках.
- Добавьте переподключение и восстановление буфера при обрыве WebSocket.