инициализация устройства
отличная задача для «первого запуска»: за пару секунд понять, где реально есть звук (а не просто «устройство в списке»), и где нас слышно. ниже — компактный, но надежный алгоритм и схема состояний для инициализации аудио в AdaOS.
цель первого запуска
- выбрать рабочую пару: {вывод → динамики слышно} + {ввод → микрофон живой}.
- сохранить профиль как «проверенный» и использовать по умолчанию.
- если не получилось — дать понятную альтернативу связи с пользователем (экран/вибро/бот/локальный веб-интерфейс).
принципы
- активная проверка, а не только перечисление: просто наличие девайса ничего не значит.
- корреляционная «сквозная» проверка: проиграли тест‑сигнал → услышали его микрофоном → убедились, что это не «цифровая петля».
- балльная система (scoring) с тайм-аутами и несколькими попытками.
- кэширование рабочего профиля (device ids + параметры) и быстрый путь при следующем старте.
пошаговый алгоритм
0) подготовка
- запросить разрешения на аудио; проверить эксклюзивные блокировки (Windows WASAPI exclusive, PulseAudio/pipewire/Android AudioFocus).
-
собрать список устройств:
-
выводы (sinks) и их свойства: тип (встроенный, HDMI, BT, USB), «подключен ли кабель», уровень громкости, mute.
- вводы (sources): есть ли активность по VU-метру, mute, тип (встроенный/гарнитура/USB/BT).
- нормализовать форматы: предпочтительно 16‑кГц/16‑бит моно для STT; для теста допускаем 44.1/48 кГц, потом приведём.
1) приоритезировать кандидатов
каждому устройству присвоить базовый вес:
- вывод: встроенные динамики (80), BT‑гарнитура со статусом «connected, a2dp» (70), USB DAC (60), HDMI «без EDID аудио» (10).
- ввод: встроенный микрофон (80), гарнитура «hfp/hsp» (70), USB‑микрофон (65), «стереомикс/loopback» (5).
доп. бонусы/штрафы:
+15если не mute и громкость/чувствительность > 30%.+20если устройство уже встречалось в кэше как рабочее.-40если драйвер помечает как «unplugged»/«suspended».-60если это явный «цифровой loopback/monitor» для ввода.
сформировать top‑N пар {sink, source} по сумме баллов (например, N=4).
2) активная проверка пары (сквозной тест)
для каждой пары по порядку:
2.1 проиграть короткий тест‑сигнал (150–300 мс) и затем «маяк» 1.5–2 с:
- «pop» (импульс), затем линейный свип (chirp) 200–4000 Гц. включить AEC, если доступно.
2.2 параллельно читать микрофон и оценить:
- мощность над шумовым порогом (SNR).
- временную корреляцию с эталонным сигналом (коэффициент ≥ 0.4–0.6).
- задержку (latency estimate 30–250 мс — типично для воздушного звука; ~0–10 мс подозрительно и указывает на «цифровую петлю»).
решение:
- если мощность ок И корреляция ок И задержка «воздушная» → вывод подтверждён.
- затем попросить пользователя реплику (например, «скажите “проверка”»), оценить VAD/энергию/частоты → ввод подтверждён.
- если вывод ок, а ввод слаб: предложить увеличить чувствительность/размьютить/снять плёнку/выбрать другой микрофон и повторить.
- если ввод ок, а вывода нет: увеличить громкость, снять mute, попробовать другой sink.
ограничения: 2 попытки на пару; общий бюджет времени ~8–12 с на пару.
3) голосовое подтверждение (человеческая валидация)
если «сквозной» тест прошёл, ассистент произносит: «я говорю через “<название устройства>”. вы меня слышите? скажите “да”». распознаём «да» → +надёжность. если нет ответа — не падаем, просто идём дальше.
4) фиксация профиля
сохраняем:
{
"audio_profile": {
"sink_id": "...",
"source_id": "...",
"format_out": "48000/2",
"format_in": "16000/1",
"latency_ms": 120,
"score": 92,
"verified_at": "2025-08-13T19:45:00Z",
"proof": { "corr": 0.73, "snr_db": 18.5 }
}
}
и помечаем как verified=true. на следующем старте — быстрый путь (см. ниже).
5) если ничего не прошло
- включить резервный канал: экран/терминал/вибро/локальный веб‑мастер
http://localhost:…/audio-setupлибо Telegram‑бот. - предложить выбор из списка устройств + кнопка «проиграть тестовый звук» для каждого.
- показать советы: включить громкость, размьютить, воткнуть кабель, переключить BT‑профиль с HFP на A2DP, закрыть приложение, удерживающее микрофон (Zoom/DAW).
конечный автомат (FSM)
START
→ ENUM_DEVICES
→ RANK_CANDIDATES
→ TEST_PAIR (sink_i, source_j)
→ OUTPUT_OK? (corr & SNR & latency) ──no──> NEXT_PAIR
→ INPUT_OK? (VAD & SNR) ──no──> HINT_RETRY (1x) → TEST_PAIR
→ HUMAN_CONFIRM (ask "да")
→ SUCCESS → SAVE_PROFILE → READY
→ no pairs left → FALLBACK_UI → READY_WITH_LIMITS
быстрый путь при следующем старте:
- если есть
verifiedпрофиль моложе, скажем, 30 дней → DRY‑RUN TEST: 300‑мс «pop» + замер корреляции (без речи). если ок → используем сразу; если нет → полноценный TEST_PAIR.
анти‑ложноположительные проверки
- цифровой лупбэк: очень малая задержка (<15 мс) при высокой корреляции → дисквалифицировать такой «микрофон».
- HDMI без звука: sink «active» но корреляции нет при громкости ≥ 50% → штраф, переход к следующему sink.
- BT HFP: если нужен качественный TTS, попросить переключиться на A2DP (или разделить: вывод — устройство А2DP, ввод — встроенный микрофон).
- AEC/агрессивный шумодав: для теста на ввод отключить/ослабить VAD‑порог, а подтверждать по широкополосной энергии и частотной маске.
интеграция в AdaOS (минимум кода/структуры)
интерфейсы
audio.enumerate()→ списки sinks/sources с атрибутами (type, name, id, muted, volume, connected).audio.play_tone(chirp_spec)иaudio.record(buf, duration_ms).audio.metrics(signal_ref, signal_in)→ {corr, latency_ms, snr_db}.audio.vad_listen(timeout_s)→ bool/phrase.audio.save_profile(profile)/audio.load_profile().
политика выбора
- «одна попытка автоматически», затем «ручной режим» через UI/бот.
- кэшируем до 3 последних успешных профилей на случай смены окружения.
UX‑мелочи, которые сильно помогают
- короткие «маяки» (пик + свип) слышны даже на очень тихом уровне.
- говорить простыми фразами и давать время: «если слышите — скажите “да”».
- если ни звука, ни микрофона — вибро/LED‑морзянка + QR‑код локального мастера настройки.
- конфиденциальность: тестовые записи автоматически удаляем, показываем пользователю уведомление об этом.
платформенные примечания (если понадобится реализовать быстро)
- Linux (PulseAudio/pipewire/ALSA):
pactl list sinks/sources,wpctl status; следить заavailability=running. - Windows (WASAPI): перечисление через MMDevice; проверять
DataFlow(Render/Capture),State(Active),DefaultCommunicationsDevice; громкость/мьют черезIAudioEndpointVolume. - Android:
AudioManager(routes, mode, bt sco),AudioDeviceInfo.TYPE_*; запросRECORD_AUDIO,MODIFY_AUDIO_SETTINGS; использоватьAudioFocusиAcousticEchoCancelerпри записи.
если хочешь, могу накидать «скелет» модуля audio_probe.py с реализацией chirp‑теста и скоринга (под sounddevice/pyaudio) — вставишь в AdaOS как шаг prepare_environment().
Альтернатива без аудиоанала
отличная идея. давай сразу заложим два «резервных канала» в сценарий первого запуска и восстановления: Wi‑Fi SoftAP с локальным мастером и BLE‑настройка (GATT). Ниже — короткий, применимый план под AdaOS.
лестница резервных каналов
- обычный аудио‑мастер (как обсуждали).
- Wi‑Fi SoftAP + локальный веб‑мастер (каптив/локалхост).
- BLE‑мастер (GATT) через телефон/планшет рядом.
- Telegram/локальный USB‑CLI как «последний шанс».
Wi‑Fi SoftAP (горячая точка)
когда включаем: если аудио‑инициализация не дала результата за ~30–60 с или пользователь не ответил.
как работает:
-
поднимаем точку доступа на устройстве:
-
SSID:
AdaOS-Setup-<4hex> - WPA2‑PSK: 8–12 символов, показываем на экране/печатаем в лог/QR.
- DHCP раздаёт, например,
10.42.0.1/24. -
HTTP(S) мастер на
10.42.0.1:80(+дублируем на:443с self‑signed). -
опционально каптив‑переадресация на
/. -
UI (минимум):
-
выбор аудио‑ввода/вывода → «проиграть тестовый звук»/«проверить микрофон».
- список видимых Wi‑Fi сетей → ввод пароля → подключение клиентом → автоматически гасим SoftAP, когда WAN поднялся.
- кнопка «сохранить профиль» и «повторить поиск».
-
API (локальный, без интернета):
-
GET /api/audio/devices→ sinks/sources. POST /api/audio/test→ проиграть chirp.POST /api/audio/select→ выбрать {sink,source}.GET /api/wifi/scan,POST /api/wifi/connect.GET /api/status→ прогресс/ошибки.-
безопасность:
-
одноразовый PSK (меняется при каждом старте мастера).
- мастер выключается по
idle_timeout(например, 10 мин). - ограничить файрволом доступ только к мастеру (никаких открытых служб).
UX‑мелочи: печатать/логировать QR с WIFI:T:WPA;S:<SSID>;P:<PSK>;; + URL http://10.42.0.1.
BLE‑мастер (GATT)
когда включаем: если нет экрана/SoftAP, но есть BLE.
сервис и характеристики:
-
Service
AdaOS Setup(UUID кастомный). -
DeviceInfo(RO): модель, версия, короткий статус. AudioList(RO, chunked JSON): список устройств.SelectAudio(WO):{sink_id, source_id}.WiFiScan(RO notify): эмитит найденные SSID.WiFiCreds(WO):{ssid, psk}→ триггер подключения.Result(notify): события «подключено/ошибка».OneTimePIN(RO): 6‑значный PIN для привязки клиента.- защита: показываем 6‑значный PIN/confirm‑pairing; закрываем GATT после успешной настройки или по таймеру.
клиент: простое мобильное приложение или готовый BLE‑сканер с шаблоном характеристик; можно добавить web‑BLE страничку в SoftAP как «второй путь».
сценарии включения/выключения
-
INIT → AudioProbe (≤60 c) -
успех →
SAVE_PROFILE → READY - нет →
Start SoftAP (idle 10 мин)и/илиStart BLE - при успешном
WiFi connect→ гасим SoftAP/BLE, пробуем аудио ещё раз (быстрый тест), сохраняем профиль. - по истечении
idle_timeout→ выключаем резервные каналы, оставляем подсказку перезапуска (adaos --setupили кнопка).
хранение состояния
{
"setup": {
"mode": "audio|softap|ble",
"softap": { "ssid": "...", "psk": "...", "started_at": "..." },
"ble": { "pin": "123456", "started_at": "..." },
"attempts": 3,
"verified_profile": { ... } // как раньше
}
}
минимальные интерфейсы в AdaOS
net.softap.start(ssid, psk, cidr, idle_timeout)/net.softap.stop()ble.setup.start(pin, idle_timeout)/ble.setup.stop()wifi.scan()/wifi.connect(ssid, psk)/wifi.status()- уже описанные
audio.*методы (enumerate/test/select/save)
отказоустойчивость и крайние случаи
- нет радио/драйверов: сразу предлагаем USB‑CLI (
adaos setup --tty) с теми же шагами. - каптив‑порталы некоторых устройств не открываются автоматически — дублируем SSID/PSK/URL голосом/LED‑миганием/в лог.
- BLE спарили, но связь «сыпется» — все операции подтверждаем событиями в
Resultи даём ретраи. - безопасность по умолчанию: одно подключение за раз; журналировать только технические события, не хранить пароли в логах.