Newer
Older
voice / docs / 06_technical_notes.md

Технические заметки

Почему base64 PCM вместо WAV/Opus

  • Низкая задержка: не нужно ждать формирования заголовка WAV или энкодинга Opus.
  • Простота клиента: Python-агент может напрямую скормить PCM в pyaudio / sounddevice / alsaaudio.
  • Компромисс: трафик больше, чем с Opus, но в локальной сети это некритично.
  • Opus можно добавить позже как опциональный формат.

Почему один TTS worker

  • CUDA context не любит параллельные вызовы из разных потоков.
  • Один worker с asyncio.Queue гарантирует последовательный доступ к GPU и предсказуемое потребление VRAM.
  • Если в будущем понадобится масштабирование — можно запустить несколько независимых инстансов.

Управление остановкой

stop_event — центральный механизм:

  • SessionState.stop() устанавливает флаг.
  • Все длительные операции (TTS, отправка аудио) могут его проверять.
  • После stop новое сообщение text автоматически сбрасывает флаг и начинает новую фразу.

Сегментация

Цель — найти баланс между:

  • задержкой (короткие сегменты синтезируются быстрее),
  • качеством (TTS лучше звучит на целых предложениях),
  • реалтаймом (не ждём слишком долго).

Параметры по умолчанию:

  • min_segment_length = 30
  • max_segment_length = 200
  • max_buffer_wait_ms = 500

Для русского языка предложения обычно короче, чем на английском, поэтому max_length выбран консервативно.

F5-TTS: особенности интеграции

  • Используется готовая модель F5TTS_v1_Base через официальный пакет f5-tts.
  • Модель автоматически загружается из Hugging Face при первом вызове load(); можно скачать заранее скриптом scripts/download_f5_tts.py.
  • Референсное аудио транскрибируется автоматически, если ref_text не задан.
  • Результат транскрипции и путь к обработанному аудио кэшируются по паре (путь, эмоция), чтобы не повторять работу при смене сегментов.
  • Скорость регулируется параметром speed инференса F5-TTS.
  • Текущая целевая частота дискретизации — 24 kHz; модель сама передискретирует референс при необходимости.
  • Инференс выполняется в отдельном потоке (asyncio.to_thread) с asyncio.Lock, чтобы не блокировать event loop и не допускать параллельных CUDA-вызовов. Все send_text сериализуются через отдельный _send_lock, чтобы избежать deadlock внутри websockets.

Замеры задержки (RTX 3090, Python 3.11, CUDA 12.6)

  • Первый запуск без warm-up: ~5–6 с, большая часть уходит на Whisper-транскрипцию референса.
  • После warm-up с кэшированным референсом: первый audio-chunk ~1.1 с на коротком сегменте.
  • 4 сегмента подряд: первый finished ~1.1 с, последний ~4.4 с.
  • stop + возобновление работает без переподключения WebSocket.

Потенциальные проблемы

  • F5-TTS может не идеально произносить украинский / европейские языки из коробки — возможно потребуется fine-tuning или fallback. Сейчас протокол и сегментатор не ограничивают язык; качество зависит от самой модели.
  • RTX 3060 (12 GB) подойдёт для базовой модели, но batch-size и длина референса придётся ограничивать.
  • Быстрый stop во время CUDA kernel не прервёт уже запущенный kernel, но предотвратит отправку результата.
  • main.py создаёт engine до старта uvicorn; при TTS_BACKEND=f5_tts первый запуск может занять десяток секунд из-за загрузки модели и vocos.