VEU.WathBody/1-docs/4-modules/m11-voice-input/api_specs/02-stt-model.md

97 lines
6.2 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Спецификация 02: SttEngine / VoskEngine и ModelStore
Источники: Ф-1.1 (активация кнопкой, офлайн, пока экран открыт),
ОВ-2 (Vosk, малая ru-модель), tech-req 5 (`VoiceInput` — service
в процессе приложения; грамматика JSON-словарь), решение гейта 1.3
№1/№4. Единицы 2–3.
## 1. SttEngine — интерфейс (для тестов и подмены)
```kotlin
interface SttEngine {
/** Прослушивание; onResult — финальные гипотезы (до 3) в тишине. */
fun startListening(onResult: (List<String>) -> Unit)
fun stopListening()
fun shutdown()
}
```
- колбэк — в главном потоке; повторный `startListening` без
`stopListening` игнорируется; `shutdown` освобождает нативные
ресурсы (вызывается при уходе с экрана/закрытии приложения).
## 2. VoskEngine — реализация
- зависимость `com.alphacephei:vosk-android:0.3.47` (Maven Central;
JNI-библиотеки в AAR — рост APK ≈ 45 МБ debug без минификации);
- `org.vosk.Model` инициализируется из каталога модели (§3) в
IO-потоке (не главном); `SpeechService(recognizer, 16000f)` —
микрофон 16 кГц; остановка — `stop()` + `shutdown()` (errata
2026-10-19: методов stopListening/destroy в API 0.3.47 нет);
`RecognitionListener` — 5 методов: команды приходят в
`onResult` (после паузы речи), `onFinalResult` — только при
остановке сервиса, `onPartialResult`/`onError`/`onTimeout`
игнорируются (потеря аудио — ждём следующую фразу);
- **грамматика (решение гейта 1.3 №4):** при создании — попытка
`Recognizer(model, 16000f, grammarJson)`; grammarJson строится из
шаблонов Ф-1.2 + числительные 0–999 (продукции: сотни/десятки/
единицы, «с половиной», «на», «секунд|секунды», «рпэ»); формат —
GOF `setGrammar` Vosk; при отказе инициализации с грамматикой
(малая модель не гарантирует поддержку) — автоматический откат
на свободное распознавание (`Recognizer(model, 16000f)`),
лишнее отсекает парсер (спец 01, Ф-1.3); выбранный режим —
в поле `grammarApplied` (диагностика в настройках);
- n-best: `setMaxAlternatives(3)`, если доступно в API vosk-android;
иначе список из одного текста; финальный результат — разбор JSON
(`text` / `alternatives[].text`);
- микрофон/разрешение: движок не запрашивает `RECORD_AUDIO` сам —
старт при отсутствии разрешения → `SecurityException` перехват
→ `startListening` возвращает false-статус (UI запрашивает до
старта, спец 05);
- только офлайн: сетевых вызовов нет; движения речи не сохраняются
(аудио не пишется на диск).
## 3. ModelStore — доставка модели (решение гейта 1.3 №1)
```kotlin
class ModelStore(context, settings) {
fun modelDir(): File // filesDir/models/vosk-ru
fun isDownloaded(): Boolean // каталог + am/ присутствуют
suspend fun download(onProgress: (Int) -> Unit): Result<File>
fun sizeMb(): Long? // для статуса настроек
}
```
- модель `vosk-model-small-ru-0.22` (~45 МБ zip): URL — настройка
`Voice.ModelUrl`, умолчание
`https://alphacephei.com/vosk/models/vosk-model-small-ru-0.22.zip`
(можно заменить на файл сервера владельца);
- загрузка OkHttp (зависимость уже есть), zip → `cacheDir`;
распаковка `ZipInputStream` c защитой от zip-slip (пути
нормализуются, выход за каталог отбрасывается); распакованный
каталог приобретает финальное имя атомарной заменой (временный
суффикс → rename);
- идемпотентность: `download` при готовой модели — no-op
(`isDownloaded` → успех без сети); повторная загрузка (смена URL)
заменяет каталог;
- прогресс — проценты по `Content-Length`; ошибки — `Result.failure`
(сеть/битый zip/нет места), текст для UI — спец 05;
- модель НЕ входит в APK и git (в `filesDir`; бэкап системы —
`allowBackup=false` уже в манифесте).
## 4. Тесты
1. ModelStore: распаковка корректного zip (фикстура-архив с
файлами-маркерами `am/`, `graph/`) → `isDownloaded=true`,
структура каталога; zip-slip (запись `../../evil`) — файл
отброшен, исключения нет; битый zip → failure; повторный
download — no-op. Чистые JVM-тесты на временных каталогах
(`@TempDir`), OkHttp — через `MockWebServer` (добавить
testImplementation).
2. VoskEngine — тонкая обёртка над нативным API, unit-тестами не
покрывается (нативные библиотеки); разбор JSON-результата
(`text`/`alternatives`) — выделен в чистую функцию
`VoskResult.parse(json)` и тестируется на JVM.
3. Интеграция движка с моделью — ручной смок владельца (критерий
приёмки 2 module_reqs).