Files
view-mate/TASK-audio-dump.md

5.4 KiB
Raw Permalink Blame History

TASK: debug-дампы аудио-пути очки→телефон (PCM-записи для диагностики STT-качества)

Подозреваемые (по фактам разведки)

(a) убогий микрофон очков (AudioRecord MIC 16 кГц s16 mono, buf 6400, чтение 3200 Б = 100 мс); (b) транспорт: PCM идёт как base64 в JSON-строке (SttAudio.data → kotlinx-serialization default ByteArray-сериализатор = base64) по NEWLINE-каналу SPP/WiFi (readLines/readLine); 100-мс-чанк 3200 Б s16 → ~4300 Б base64+JSON. Потери/задержки/перепутанный порядок кусков по шкатулке — верный кандидат «убожества»; (c) VAD/Whisper на телефоне (настройки: VAD 0.5/0.25с/0.25с/20с, Whisper-small int8, CPU, 4 потока — на старом HONOR CPU-распознавание медленное и может «не успевать»).

Цель

Запись сырого PCM в двух точках (очки: ДО отправки; телефон: ПОСЛЕ приёма и декода) + сравнение: объёмы/RMS/порядок. Дальше (утром, агент+user): снять с устройств, снять офлайн-ASR-картину, решить (a)/(b)/(c).

Оковы

  • Пути только относительные от корня репо /root/WORK/view-mate. Никаких /tmp.
  • НЕ ТРОГАТЬ: HostToGlasses.kt, GlassesServer.kt, HostConnection.kt, MainActivity.kt (оба модуля).
  • Правим: SttMicStream.kt (очки), SttStreamer.kt (телефон), PhoneApp.kt (телефон, НЕ охраняется), новый WavWriter.kt (очки).

Изменения

1. app-glasses/src/main/kotlin/pw/binom/viewmate/glasses/stt/WavWriter.kt (новый)

Минимальный WAV-райтер (PCM s16le, 16000 Hz, mono):

  • class WavWriter(path: File, sampleRate: Int = 16000, channels: Int = 1)
  • fun write(pcm: ByteArray) — appends (header пишется один раз при создании файла).
  • close(). Стандартный RIFF/PCM header 44 байта.

2. app-glasses: SttMicStream.kt

  • В start(filesDir, shouldRun): перед запуском потока проверить маркер File(filesDir, "stt_dump.marker"). Если есть:
    • создать File(filesDir, "stt_dump_" + java.time формат HHmmss + ".wav") через WavWriter;
    • ВСЕ чанки, уходящие в onChunk (только ветка ЖИВОГО микрофона, эмуляторную не трогаем), дописывать в WavWriter;
    • log("stt", "dump микрофона: ").
  • При stop() — закрыть WavWriter (runCatching).
  • Все операции с dump — runCatching, сбои — только log, поток звука не прерывать.

3. app-phone: SttStreamer.kt

  • Конструктор: добавить опциональный параметр dumpPcmTo: File? = null (хвост конструктора, чтобы не сломать существующие вызовы).
  • В accept(pcm): если dumpPcmTo != null — дописать pcm в файл (DataOutputStream/FileOutputStream append, raw s16le, header НЕ нужен — файл помечен как 16кГц s16 mono в логе). Все runCatching.
  • В close(): закрыть поток дампа.
  • Лог: "stt" логгер — "PCM-дампа: ".

4. app-phone: PhoneApp.kt (ensureStt(), ~строка 368)

  • При создании SttStreamer передать dumpPcmTo:
    val dumpMarker = File(filesDir, "stt_dump.marker")
    val dumpPcm = if (dumpMarker.exists()) File(filesDir, "stt_phone_dump.raw") else null
    
    (посмотреть по факту, какой context/filesDir в области видимости).
  • Лог: "stt" — "PCM-дампа (телефон): <path или off>".

5. Прогон

  • ./gradlew :app-glasses:testDebugUnitTest :app-phone:testDebugUnitTest :lib-core:jvmTest — зелёные.
  • ./gradlew :app-glasses:assembleDebug :app-phone:assembleDebug — BUILD SUCCESSFUL.

6. Коммит

  • git add -A && git commit -m "stt: debug-дампы аудио-пути (очки: сырой микрофон в WAV; телефон: принятые PCM) — старт по stt_dump.marker"
  • PUSH не делать.

Для утренней диагностики (агент + user)

  • На очках: touch маркера (adb shell run-as или в самом каталоге /sdcard/Android/data/.../files/stt_dump.marker — агент уточнит path).
  • На телефоне: аналогично.
  • Пользователь произносит фразы в очки → очки делают dump; после сессии — adb pull обоих файлов.
  • Сравнение: (1) объём/продолжительность (должны совпадать с точностью на куски), (2) RMS/клиппинг в очках-файле (диагноз (a)), (3) побайтовое/почанковое сравнение очков↔телефон (диагноз (b)), (4) запустить обе записи через серверный ASR (88.125:8006) — «убожество» воспроизводимо в оффлайне?