5.4 KiB
5.4 KiB
TASK-full-buffer-stt.md — полный текст по клику: распознавание всего буфера (не VAD-сегментов)
Симптом (24.08, подтверждено дампами)
Пользователь: TripleClick → говорит → Click → «думаю...» → сразу возврат в «слушаю». Полный текст пуст (stt ВЕСЬ ТЕКСТ: пусто в логах телефона).
Диагноз (по дампам голоса пользователя)
- Микрофон RayNeo X2 пишет РВАНО: пачки 50–100мс с провалами до нуля (в дампе 213110, 16.7с — 66% тишины). Это железо/драйвер очков, не транспорт (очки→телефон совпадают 98%).
- Silero VAD (тот же
silero_vad.onnxс телефона, прогнан на хосте) на усиленных дампах нарезает речь на 10+ сегментов по 0.3–0.4с (213110: 12 сегментов). - Whisper (faster-whisper-base на хосте) на ПОЛНОМ дампе распознаёт «Ого! | А что ты делаешь?» (212441, 5.9с) и «...грека...» (213110) — НЕ пусто. На отдельных VAD-сегментах 0.3–0.4с whisper возвращает пусто/мусор.
- Вывод: кликовый режим не должен зависеть от VAD-сегментации. Начало/конец фразы задаёт пользователь кликами; полный текст = распознавание ВСЕГО накопленного PCM одним вызовом whisper. VAD остаётся только для промежуточных фраз на очки (не трогать).
Задача
app-phone SttStreamer.kt:
- Буфер всей сессии: в
accept(pcm)накапливать принятые сэмплы (FloatArray) в полеsessionPcm(сбрасывается вresetLocked()). Не терять при VAD-обработке (VAD-путь остаётся как есть, параллельно). finish(): распознать полный буфер одним вызовом whisper:sessionPcmцеликом, если ≤ 30с (16000*30 сэмплов);- если длиннее — последние 30с (хвост:
sessionPcm.takeLast(16000*30)), чтобы команда пользователя (сказанная перед кликом) точно попала; - результат:
stt.recognize(chunk)— один вызов, текст вfull(перезаписать, не конкатенировать с VAD-фразами). finish()по-прежнему ждётlastFuture(VAD-распознавания в executor), затем выполняет полный прогон.
resetLocked(): очищатьsessionPcm.- Логи: при распознавании полного буфера
log("stt", "полный буфер: ${N} сэмплов (${dur}с) → '${text}'").
НЕ трогать: охраняемые 4 файла (HostToGlasses.kt, GlassesServer.kt, HostConnection.kt, MainActivity.kt), протокол lib-core, app-glasses, WhisperStt, StreamingVad/SherpaStreamingVad, VAD-путь и onPhrase (промежуточные фразы).
Точки правки
app-phone/src/main/kotlin/pw/binom/viewmate/phone/stt/SttStreamer.kt— буфер + finish() + resetLocked().- Тесты: новый
app-phone/src/test/kotlin/pw/binom/viewmate/phone/stt/FullBufferSttTest.kt(JVM, фейковыйPhraseRecognizer,FakeVadкак в существующих тестах — см.stopSttFullTextSendsSttDoneфикстуру).
Тесты (JVM)
accept3 чанка по 3200Б (9600Б = 0.3с) →finish()→ фейковый recognizer получил ОДИН вызов с полным буфером (9600 байт → 4800 сэмплов),full= текст фейка.- Длинный буфер > 30с:
accept40с →finish()→ recognizer получил ровно 16000*30 сэмплов (хвост), текст не пуст. reset()→finish()→ recognizer НЕ вызывался,fullпуст.- VAD-путь не сломан:
FakeVadс одним сегментом →onPhraseпо-прежнему вызывается (промежуточная фраза бродкастится), ноfullпослеfinish()= результат полного буфера.
Проверка
./gradlew :app-phone:testDebugUnitTest— зелёные (было 158 + новые).- Полная:
./gradlew :app-phone:assembleDebug :app-glasses:assembleDebug :lib-core:jvmTest. - Коммит:
stt(phone): полный текст по клику — whisper на всём буфере, VAD только для промежуточных фраз. Push НЕ делать.
Приёмка на железе (Hermes + пользователь)
- Установить оба APK, force-stop,
am start. - Пользователь: TripleClick → фраза («поставь спокойную музыку») → Click.
- Ожидание:
stt ВЕСЬ ТЕКСТ: поставь спокойную музыку(не пусто) → LLM → ответ на очки.