From 57e7fe1d17282dd4447e036e42390e95a1923f71 Mon Sep 17 00:00:00 2001 From: Porfiry Date: Sat, 19 Sep 2026 01:57:18 +0300 Subject: [PATCH] =?UTF-8?q?MIC-ASR-SEARCH.md:=20=D1=81=D0=B2=D0=BE=D0=B8?= =?UTF-8?q?=20=D0=B3=D0=BE=D1=82=D0=BE=D0=B2=D1=8B=D0=B5=20=D0=B1=D0=B8?= =?UTF-8?q?=D0=B1=D0=BB=D0=B8=D0=BE=D1=82=D0=B5=D0=BA=D0=B8=20mic-kmp/asr-?= =?UTF-8?q?kmp/vad-kmp=20=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=20=D0=BC=D0=B8=D0=BA=D1=80=D0=BE=D1=84=D0=BE?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B8=D0=B7=20assistent?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BORROW-FROM-ASSISTENT.md | 30 +++---- MIC-ASR-SEARCH.md | 174 +++++++++++++++++++++++++++++++++++++++ README.md | 3 + REQUIREMENTS.md | 10 +-- 4 files changed, 198 insertions(+), 19 deletions(-) create mode 100644 MIC-ASR-SEARCH.md diff --git a/BORROW-FROM-ASSISTENT.md b/BORROW-FROM-ASSISTENT.md index 2b3017e..e9fa175 100644 --- a/BORROW-FROM-ASSISTENT.md +++ b/BORROW-FROM-ASSISTENT.md @@ -231,25 +231,24 @@ clipboardManager.setText(AnnotatedString(segment.code)) (`bold`/`italic`/`code`/`strikethrough`/`link`). Логику стилей держат в данных, а не в Композе — удобно и переносимо. -## 4. Запись микрофона на десктопе — без единой библиотеки +## 4. Запись микрофона — код из assistent брать НЕ надо **Где:** `client/src/main/kotlin/pw/binom/client/audio/MicrophoneRecorder.kt` (83 строки). +Всё на стандартной Java (`javax.sound.sampled`: `AudioFormat`, `AudioSystem`, +`DataLine`, `TargetDataLine`), формат PCM 16 бит / 16 кГц / моно, буфер 4096 байт. -Ценнейшая находка для нашей кнопки записи. Всё на стандартной Java: +**Но копировать это не нужно.** У нас есть своя готовая библиотека **`mic-kmp`** +(опубликована, версия `1.0.0`), которая делает то же самое, только лучше: +отмена структурная — микрофон освобождается сам через `finally`, без ручного `close()`. -```kotlin -import javax.sound.sampled.AudioFormat -import javax.sound.sampled.AudioSystem -import javax.sound.sampled.DataLine -import javax.sound.sampled.TargetDataLine -``` +Здесь файл полезен **только как источник приёмов**: -Формат: **PCM 16 бит, 16 кГц, моно, little-endian**, буфер 4096 байт. -Наружу отдаёт `ReceiveChannel` — чанки сразу уходят на распознавание, -без накопления файла. Микрофон — `AudioSystem.getLine(...) as TargetDataLine`. +- подтверждает формат, который ждёт распознавание: **16 кГц, моно, PCM 16 бит, little-endian** + (`AudioFormat.Encoding.PCM_SIGNED, sampleRate, 16, 1, 2, sampleRate, false`); +- показывает, что сторонние библиотеки для звука не нужны — хватает стандартной Java; +- отдаёт данные кусками (`ReceiveChannel`), а не файлом — можно стримить. -**Почему важно:** сторонние библиотеки для звука не нужны вообще. Мы это -собирались решать позже, но приём уже есть и он рабочий. +Подробности и координаты библиотек — `MIC-ASR-SEARCH.md`. --- @@ -295,5 +294,8 @@ import javax.sound.sampled.TargetDataLine | Отрисовку Markdown (4 файла) | Экраны целиком (`ChatScreen`, `SettingsDialog`, …) | | Приёмы: таблицы, ленивый список, автоскролл, полоса прокрутки | Модули `chat-*` и модели сообщений | | Кликабельные ссылки, копирование кода, выделение текста | `MessageBubble` | -| Запись микрофона через `javax.sound` | `ChatViewModel`, `config/*`, `cache/*` | +| Приём записи звука (формат 16к/моно, `javax.sound`) | `ChatViewModel`, `config/*`, `cache/*` | | Зависимость `ru.otpbank.ai:markdown:0.51.0` | Чужой дизайн и окна | + +**Микрофон и распознавание — не из assistent.** У нас для этого есть свои +опубликованные библиотеки: `mic-kmp`, `asr-kmp`, `vad-kmp`. Разбор — `MIC-ASR-SEARCH.md`. diff --git a/MIC-ASR-SEARCH.md b/MIC-ASR-SEARCH.md new file mode 100644 index 0000000..5e7d662 --- /dev/null +++ b/MIC-ASR-SEARCH.md @@ -0,0 +1,174 @@ +# Микрофон и распознавание: результаты поиска + +**Задача была:** найти готовые библиотеки для работы с микрофоном и распознавания, +прежде чем брать код из `ai/assistent`. **Код не пишем** — только поиск. + +**Ответ короткий:** и то, и другое **уже есть у нас, готовое и опубликованное**. +Из `ai/assistent` брать код микрофона **не нужно вообще**. + +--- + +## 1. Микрофон: библиотека есть — `mic-kmp` + +**Репозиторий:** https://git.binom.pw/subochev/mic-kmp +**Каталог:** https://git.binom.pw/subochev/libraries (раздел «📦 Микрофон») + +KMP-библиотека: поток микрофона как `Flow` (s16le mono PCM). +Три файла, вот и вся библиотека: + +| Файл | Что делает | +|---|---| +| `mic-api/src/commonMain/.../MicAudio.kt` | интерфейс `MicDevice` + объект `MicAudio.record(...)` | +| `mic-api/src/jvmMain/.../MicJvm.kt` | JVM — `javax.sound.sampled` (`TargetDataLine`) | +| `mic-api/src/androidMain/.../MicAndroid.kt` | Android — `AudioRecord` | + +**Таргеты: `jvm` и `android`.** JVM-таргет — ровно то, что нужно десктопу. +JVM-код собран на `JvmTarget.JVM_17`. + +**Опубликована в Nexus `caffeine`, версия `1.0.0`:** +- `pw.binom.mic:mic-api:1.0.0` (KMP metadata) +- `pw.binom.mic:mic-api-jvm:1.0.0` (JVM — **наше**) +- `pw.binom.mic:mic-api-android:1.0.0` + +Проверено: `maven-metadata.xml` отдаёт `HTTP 200`, latest = `1.0.0`. + +**Использование — одна строка:** + +```kotlin +MicAudio.record(sampleRate = 16000, channels = 1, chunkMillis = 100) + .onEach { chunk: ByteArray -> /* s16le mono PCM, ~100 мс */ } +``` + +**Чем лучше кода из assistent:** отмена **структурная**. Остановил корутину-коллектор — +микрофон освободился сам, в `finally`. Не надо помнить про `close()`. В assistent +(`MicrophoneRecorder`, 83 строки) — ручное управление через `start()`/`stop()`, +и связка «канал + флаг recording + внешний TargetDataLine». Наш вариант чище. + +**Вывод: копировать `MicrophoneRecorder` из assistent НЕ надо — подключить `mic-kmp`.** + +--- + +## 2. Распознавание: библиотека есть — `asr-kmp` + +**Репозиторий:** https://git.binom.pw/subochev/asr-kmp +**Каталог:** https://git.binom.pw/subochev/libraries (раздел «🎙 ASR») + +KMP, таргеты **jvm** и **android** (плюс native: linux, macos, mingw). +Потоковый интерфейс — приём аудио кусками и **промежуточный текст по ходу**: + +```kotlin +interface Stt : AutoCloseable { + fun feed(samples: FloatArray) // порция 16 кГц моно PCM, [-1, 1] + fun partialText(): String? // промежуточный текст (растёт по ходу) + fun endOfInput() // конец ввода + fun finalText(): String // итог + override fun close() +} +``` + +**Есть `partialText` — текст появляется по мере речи**, а не после конца записи. +Для нашей кнопки микрофона это именно то поведение, которое нужно. + +### Движки (все опубликованы в `caffeine`) + +| Движок | Координаты (группа `pw.binom.asr`) | Версии в Nexus | +|---|---|---| +| `asr-api` — интерфейс | `asr-api` / `-jvm` / `-android` | до `4` | +| `asr-vosk` — Vosk | `asr-vosk` / `-jvm` / `-android` | есть | +| `asr-qwen3` — Qwen3-ASR | `asr-qwen3` / `-jvm` | есть | +| `asr-whisper` — Whisper | `asr-whisper` / `-jvm` / `-android` | есть | +| `asr-openai` — удалённое (OpenAI-совместимое) | `asr-openai` | есть | + +Под капотом нейронок — `sherpa-onnx` (`com.github.k2-fsa.sherpa-onnx`, `v1.13.6`), +для JVM отдельно тянется нативная библиотека `sherpa-onnx-native-lib-linux-x64`. + +### Про модели (важно) + +- **Qwen3-ASR** — модель уже на нашем статик-сервере, качать из-за бугра не надо: + `http://static.binom.pw/models/qwen3_06/` (~940 МБ: encoder 175M, decoder 721M, frontend 43M). +- **Whisper small int8** — модели на статике НЕТ, качать самостоятельно. +- **Vosk** (`vosk-model-small-ru-0.22`) — **входит в комплект**, распаковывается сам. + Самый простой путь для первого запуска: ничего качать не надо. + +--- + +## 3. Рядом есть ещё три полезные библиотеки + +Из того же каталога `subochev/libraries`: + +### VAD — детектор речи, `vad-kmp` +https://git.binom.pw/subochev/vad-kmp — «VAD — Silero», KMP jvm+android, +**модель внутри артефакта** (2.3 МБ, ничего качать не надо). + +```kotlin +VadInstance(16000).use { vad -> + val prob = vad.processingWindow(windowPcmFloats) // FloatArray → [0..1] +} +``` +Координаты: `pw.binom.voice.vad:vad-jvm`, версия `2`. +**Зачем:** определять, говорит человек или молчит — не гнать тишину в распознавание. + +### Есть и второй VAD +`pw.binom.ai.voice:vad` — «детектор речи (VAD) — `vad` / `vad-jvm`». Два разных VAD, +при выборе разобраться, какой свежее. + +### OpenAI-клиент +`pw.binom.openai:api` / `api-jvm` + `ktor-impl` (HTTP-движок). Нужен для `asr-openai` +(распознавание на удалённом сервере, а не локальной нейронкой). + +### Speaker-эмбеддинги и text-embedding +- `subochev/embedding-kmp` — voice embedding (кто говорит). +- `subochev/text-embedding-kmp` — text embedding (SigLIP2). +Нам сейчас не нужно, но пусть будет известно. + +--- + +## 4. Как это собрано вместе — пример `view-mate` + +**Репо:** `/root/WORK/view-mate` (очки RayNeo X2), модули `app-glasses`, `app-phone`, `app-host`, `lib-core`. + +**Важно:** view-mate **не подключает** `mic-kmp`/`asr-kmp` как библиотеки — у него +своя реализация `SttMicStream` на `android.media.AudioRecord` +(`app-glasses/.../stt/SttMicStream.kt`). То есть `mic-kmp` — это **вынесенная +и обобщённая** версия того же подхода. + +Из полезного в `SttMicStream.kt` — комментарий на строке 229: +`AudioRecord.read(byte[], int, inFrameCount)` возвращает **число кадров**, а не байт. +Классические грабли Android-записи. + +--- + +## 5. Чем пользуется сам `ai/assistent` + +Важно для понимания разницы подходов: + +`chat-server/.../stt/SttClientImpl.kt` — это **не** локальное распознавание. +Это HTTP-клиент к OpenAI-совместимому API: + +```kotlin +client.post("$base/audio/transcriptions") { ... } // multipart, как у OpenAI +``` + +Настройки через переменные окружения: +- `STT_API_KEY` — без него голосовой адаптер выключен; +- `STT_URL` — по умолчанию `https://api.openai.com/v1`; +- `STT_MODEL` — по умолчанию `whisper-1`. + +**Вывод:** у assistent распознавание — **отправка файла на сервер**. У нас +`asr-kmp` умеет и локальные нейронки (Qwen3, Whisper, Vosk), и удалённое (`asr-openai`). +Наш вариант шире, а `mic-kmp` (поток) + `asr-kmp` (поток) + `vad-kmp` (детектор речи) +складываются в готовую цепочку: **микрофон → детектор речи → распознавание → текст**. + +--- + +## 6. Итог: что делать + +| Вопрос | Ответ | +|---|---| +| Та отдельная библиотечка для микрофона, которую мы делали — существует? | **Да.** `mic-kmp`, опубликована в `caffeine` версии `1.0.0`, jvm-таргет есть. | +| Библиотечки распознавания с нейронками где? | **`asr-kmp`** — Qwen3, Whisper, Vosk, удалённое. Всё в `caffeine`. Плюс каталог: `subochev/libraries`. | +| Есть ли где-то микрофон в каталоге библиотек? | **Да**, первым разделом — «📦 Микрофон». | +| Брать ли код микрофона из `ai/assistent`? | **Нет.** Подключаем `mic-kmp` — чище, с автоосвобождением, без внешних библиотек. | +| Нужны ли сторонние зависимости для звука? | **Нет.** Всё на стандартной Java (`javax.sound.sampled`) и sherpa-onnx для нейронок. | + +**Код не писан** — только поиск, как и просили. diff --git a/README.md b/README.md index bf8e54a..a49d533 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ sketches/004-settings-agents/index.html # настройки агентов `BORROW-FROM-ASSISTENT.md` — что берём из `ai/assistent` (приёмы, плагины, версии), а что не берём (архитектура, экраны, дизайн — своё). +`MIC-ASR-SEARCH.md` — библиотеки для микрофона и распознавания: своё готовое +(`mic-kmp`, `asr-kmp`, `vad-kmp`), вместо копирования кода из assistent. `MARKDOWN-SOURCE.md` — откуда брать готовую отрисовку Markdown (файлы и адреса). `REQUIREMENTS.md` — требования. Статус: накидываем, ни один пункт не обязателен к исполнению в том виде, как записан. @@ -72,3 +74,4 @@ sketches/004-settings-agents/index.html # настройки агентов - **Откуда берётся распознавание речи.** Отдельная тема, к интерфейсу не относится: в дизайне показано только место кнопки и что происходит на экране во время записи. Кто именно превращает голос в текст — решается потом. + Готовые библиотеки уже найдены, см. `MIC-ASR-SEARCH.md`. diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index eecf6a3..2da14bd 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -62,11 +62,11 @@ (по нажатию или на удержание) — решим позже. - **R19.** Распознавание речи — **отдельная тема, здесь не решается.** Важно только: где кнопка, что видно во время записи, что происходит после. -- **R19.1.** **Как читать микрофон — уже известно, берём приём из `ai/assistent`.** - Никаких сторонних библиотек: стандартные средства Java, класс - `MicrophoneRecorder` (83 строки). Формат — 16 кГц, 16 бит, моно. - Наружу отдаёт поток кусочков, а не файл: можно отправлять на распознавание - сразу, не дожидаясь конца записи. Подробности — `BORROW-FROM-ASSISTENT.md`, п. 4. +- **R19.1.** **Как читать микрофон — решено, пишем не мы.** Подключаем свою готовую + библиотеку `mic-kmp` (`pw.binom.mic:mic-api-jvm`, версия `1.0.0`, есть в Nexus). + Отдаёт звук потоком кусочков, а не файлом: можно распознавать сразу, не дожидаясь + конца записи. Микрофон освобождается сам при остановке. Формат — 16 кГц, моно. + Код из `ai/assistent` для этого **не берём**. Подробности — `MIC-ASR-SEARCH.md`. ## 6. Несколько агентов