Skip to content

инициализация устройства

отличная задача для «первого запуска»: за пару секунд понять, где реально есть звук (а не просто «устройство в списке»), и где нас слышно. ниже — компактный, но надежный алгоритм и схема состояний для инициализации аудио в AdaOS.

цель первого запуска

  1. выбрать рабочую пару: {вывод → динамики слышно} + {ввод → микрофон живой}.
  2. сохранить профиль как «проверенный» и использовать по умолчанию.
  3. если не получилось — дать понятную альтернативу связи с пользователем (экран/вибро/бот/локальный веб-интерфейс).

принципы

  • активная проверка, а не только перечисление: просто наличие девайса ничего не значит.
  • корреляционная «сквозная» проверка: проиграли тест‑сигнал → услышали его микрофоном → убедились, что это не «цифровая петля».
  • балльная система (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.

лестница резервных каналов

  1. обычный аудио‑мастер (как обсуждали).
  2. Wi‑Fi SoftAP + локальный веб‑мастер (каптив/локалхост).
  3. BLE‑мастер (GATT) через телефон/планшет рядом.
  4. 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 и даём ретраи.
  • безопасность по умолчанию: одно подключение за раз; журналировать только технические события, не хранить пароли в логах.