Новый домен G: Connectivity-core (WiFi/LTE/tethering + BT-адаптер/паринг) + Phone-ап (HFP-звонки/контакты). Многоагентный adversarial-ревью: 24 находки, 15 подтверждено (default-refute; tech-измерение — 0, техника устояла), все применены. Ключевое из ревью: - BT-шов резолвнут двунаправленно: владелец BlueZ — Connectivity-core (G §3 ↔ H §6/§15 ✅). - Граница голоса: только HFP спаренного телефона; встроенный модем — data-only (VoLTE/eCall вне скоупа). - SIM PIN/PUK-флоу (sim_locked/no_sim) + captive-portal детект (portal/limited, не ложный online) — модем v1. - Connectivity поднимается на Stage 2 (синхр. с architecture §6). - Мультипаринг: ровно один active-телефон для HFP/PBAP (Dial/CallStateChanged однозначны). - SCO-loss mid-call → CallState=audio_lost + снятие роли phone_call → H раскорчивает медиа (не залипает). - Входящий-оверлей не перекрывает реверс-вид (z-order overlay-слота → C); tethering-петля AP+tether запрещена. - PBAP-синк фоновый; отказ-пути паринга/no_service симметрично J/H. Кросс-док: H §6/§15 (BlueZ ✅) + якоря D §147/§148→§10; architecture §3 (Connectivity+BT) /§6 (Stage 2); ipc §3 (Type+tether, State enum, CallState=audio_lost); security-privacy §7 (контакты/журнал/SMS local-first); hardware §4 (Bluetooth); tech-stack (NM/MM/BlueZ); C §11 (z-order overlay). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
28 KiB
Домен H — Медиа / аудио
Вся стандартная мультимедиа (локальное аудио, BT A2DP/AVRCP, радио, стриминг) и сквозной арбитр аудио — политика фокуса/ducking поверх PipeWire, которой подчиняются ассистент, телефон, навигация и служебные звуки. First-party ап на SDK (Rust).
Статус: v2 (на ревью). v2 — после adversarial-ревью (20 находок).
Связано с: architecture.md (§3 Media, §4 data-plane, §5 карта связей, §9 аудио-арбитраж) · ipc.md (Media, Assistant, Phone, Power) · security-privacy.md (§3 audio_out/audio_in, §4 гейтинг) · plugin-sdk.md (§3 network, §4 точки расширения) · hardware.md (§4 codec/усилитель/mic/тюнер/amp-mute, §3 load-shedding) · tech-stack.md (PipeWire/WirePlumber, symphonia) · домены D (TTS/AEC), G (HFP/BT/проекция), I (нав-промпты), C (мультируль/слоты/distraction), B (lifecycle), J/E/K (служебные звуки) · principles.md (#3,#4,#6,#8,#11,#12,#13)
1. Назначение и границы
- Две роли: (1) медиаплеер — источники звука (локальные файлы, BT-аудио, радио, стриминг) + now-playing/транспорт; (2) арбитр аудио — системная политика фокуса/ducking, по которой звук ассистента/телефона/навигации/служебных сигналов сосуществует с медиа.
- H — design-owner аудио-политики, но enforcement живёт в WirePlumber (база, см. §3) — не в процессе плеера: падение плеера (#4) не должно ломать маршрутизацию звонка/ассистента.
- Границы: H не владеет BT-адаптером/паррингом (домен G/Connectivity; H цепляет только A2DP/AVRCP — §6)
и не генерирует служебные звуки сам — их эмитит владелец фичи (park-beep — иллюстративный пример;
владелец синтеза служебных звуков открыт, §15); H лишь даёт им приоритет роли
alert. Не safety-critical (#1): служебные звуки — информирование, не управление, без регуляторных claim'ов. - Plane: звук — PipeWire (+ WirePlumber), data-plane; control/состояние — лёгкий D-Bus (
Media, §11).
2. Аудио-плоскость: роли потоков
- Каждый продюсер звука тегирует свой PipeWire-поток двумя ортогональными ключами:
media.role(use-case — ОСНОВА арбитража §3:media/nav_guidance/assistant_tts/phone_call/projection/alert/emergency-резерв) иmedia.category(Playback/Capture— тип обработки, в арбитраже НЕ участвует; микрофон ассистента —Capture, см. §4). - Роль (
media.role) — единственное, что знает политика: арбитраж по роли, не по «кто это» → развязка (#4,#9). first-party теги доверенные; стороннему плагину доступен толькоmedia(черезaudio_out, §11). - Высокочастотного звука по D-Bus нет (architecture §4): D-Bus несёт лишь состояние/команды.
3. Политика фокуса и ducking (арбитр — ядро H)
Лестница приоритетов playback-ролей (media.role; выше вытесняет ниже):
| Роль | Приоритет | Действие над нижними |
|---|---|---|
alert (park-beep, предупреждение) |
высш. | короткий mix-over (+ duck медиа на длительность) |
phone_call / projection-звонок (G) |
высокий | cork (пауза) медиа; нав-промпт — duck/очередь |
assistant_tts (D) |
выше-средн. | duck медиа; нав-промпт — очередь |
nav_guidance (I) |
средний | duck медиа на время промпта |
media (файлы/BT/радио/стриминг) |
базовый | — |
(assistant_capture — это media.category=Capture (микрофон+AEC §4), а НЕ строка output-лестницы:
capture-нода не «дакает» вывод; дакает медиа именно playback-роль assistant_tts.)
- Cork vs duck: cork = пауза с авто-resume по освобождению; duck = временное понижение громкости с
восстановлением. Звонок корчит медиа (не микширует); ассистент/нав — дакают. duck —
относительный аттенюатор поверх текущего пользовательского уровня (master/per-stream), НЕ запомненное
абсолютное значение: изменения громкости мультирулём (§8) во время duck применяются к базовому уровню и
переживают un-duck (restore снимает аттенюатор к текущему уровню, не к уровню на старте duck); при
mute/master=0 duck/un-duck — no-op. - Intra-role
media(единственный media-фокус): одновременно активен ровно один продюсер ролиmedia; появление нового (плагин-media-source §7, BT-A2DP §6, локальный плеер §5) корчит предыдущего держателя (audio-focus-семантика), а не микшируется (микс в пределах роли — только дляalert). Координирует динамическая часть Media-апа (§11SetSource/состояние фокуса). (Ср. арбитраж 0..N видео-источников J §5.) - Конкуренция transient:
assistant_tts>nav_guidance(нав в очередь). Во время звонка медиа закорчено, ассистент подавлен;alert— микшируется поверх всего (короткий, слышен даже в звонке). - Гистерезис фокуса (анти-pumping): переходы duck/cork по
Assistant.StateChangedсглаживаются — минимальное окно удержания (hold) перед un-duck (debounce серии idle↔listening от ложных wake-word/VAD, ср. D §2) + восстановление громкости фейдом (release), не скачком. Конкретные attack/hold/release — реализация (§15 split-политики). - Crash-safe restore (две оси):
- source-ось: duck/cork привязаны к жизни потока-ноды — исчез продюсер (упал ассистент/нав) →
WirePlumber восстанавливает медиа (#4, ср. ipc §2). Проактивный cork (медиа корчится по сигналу
PhoneДО появления нодыphone_call) на верхнюю ноду не завязан, поэтому его откат гарантируют дополнительно: (1) исчезновение владельца имени Media-координатора → снятие наложенных корок поNameOwnerChanged(ipc §2); (2) watchdog-timeout, если нода звонка не материализовалась за T мс (отбой до connect) → авто-un-cork. - sink-ось: при исчезновении output-ноды (load-shed усилителя, hardware §3) наложенные cork/duck —
невалидны относительно этого выхода; при возврате output-ноды (напр. после
ShutdownAborted, B §4) политика пересчитывается заново по живым ролям-продюсерам, а не наследует залипшее состояние.
- source-ось: duck/cork привязаны к жизни потока-ноды — исчез продюсер (упал ассистент/нав) →
WirePlumber восстанавливает медиа (#4, ср. ipc §2). Проактивный cork (медиа корчится по сигналу
- Где живёт: статическая лестница ролей — конфиг WirePlumber в базовом образе (поднимается на
Stage 1, §9; переживает крэш плеера); динамическая координация (проактивный cork, гистерезис,
intra-role, состояние для UI) — компонент Media-апа, подписан на
Assistant.StateChanged/Phone/Nav/Power(ipc §3–4). До Media-апа (v1) хватает статической политики + ролей ассистента.
4. AEC и loopback-референс (резолв D §10)
- H публикует monitor/loopback-tap смешанного выхода как референс для эхоподавления (без него wake-word во время воспроизведения и barge-in невозможны).
- AEC — узел audio-графа на базе
module-echo-cancel(WebRTC APM,libspa-aec-webrtc, BSD — #12) на пути capture микрофона; референс — monitor-порты выхода. Не внутри ассистента (D потребляет уже очищенную capture-ноду). (module-echo-cancel— отдельный модуль PipeWire, неmodule-filter-chain: последний — иной механизм (граф LADSPA/LV2/builtin), пригоден лишь для опц. кастомного денойза.) → закрывает «место AEC (в audio-plane vs внутри ассистента)» из D §10 в пользу audio-plane. - Beamforming/денойз салона (D §10) — отдельно (mic-массив, hardware); AEC ≠ денойз.
5. Локальное аудио
- Библиотека с USB/
/data: индексация тегов (artist/album/track/обложка), плейлисты, очередь. Индексация — фоном/лениво (#11), не блокирует UI; БД индекса в приватном хранилище апа. - Декодер: 🟡
symphonia(чистый Rust: MP3/AAC/FLAC/Vorbis/WAV/ALAC; MPL-2.0 — допустимо по #12). MP3/AAC/ALAC — за не-дефолтными фич-флагами; AAC — патенты живы (роялти за декодер) → юр-проверка перед включением (#12); MP3-патенты истекли (2017). Opus уsymphoniaНЕ декодируется → для.opus/ Opus-в-.ogg(и Opus-интернет-радио §7) — отдельный биндинг libopusaudiopus(BSD, #12). → tech-stack. - Gapless/очередь: декодер-пайплайн/очередь предусматривают предзагрузку следующего трека на стыке (gapless — для альбомов/концертников; crossfade — опц. поверх). Включаем ли — §15.
- Отказ-пути (симметрично J §3/§4): битый/недекодируемый файл → skip трека + неблокирующая индикация (не стоп очереди); ENOSPC при индексации / исчезновение USB-носителя → gracefully остановить индексацию/ трек с видимой индикацией, не крэш (ср. J §4 «носитель отсутствует → стоп с индикацией»).
- Офлайн (#3): локальное аудио и радио не зависят от сети.
6. Bluetooth-аудио (A2DP sink + AVRCP)
- A2DP sink: телефон → головблок играет звук телефона. Кодеки: SBC (обяз., свободный) + AAC; aptX/LDAC — проприетарны/лицензируемы → по умолчанию ИСКЛЮЧЕНЫ (#12), опц. позже при лицензии.
- AVRCP: метаданные (трек/исполнитель/позиция) + транспорт (play/pause/next с головблока → телефон).
- Обрыв A2DP в середине трека (телефон ушёл из зоны / disconnect-событие от G/Connectivity) → cork/пауза транспорта + auto-resume по реконнекту, не крэш, видимая индикация.
- Шов с G/Connectivity: BT-адаптер, паринг и профиль HFP (звонок) — не H (домен G/Connectivity).
H цепляет A2DP/AVRCP к уже спаренному устройству. Сосуществование A2DP↔HFP на одном устройстве — через
политику (§3): входящий
phone_callкорчит A2DP-media. Владелец BlueZ-адаптера/паринга — Connectivity-core (домен G, резолв G §3); сосуществование A2DP↔HFP — политика §3. - Проекция (CarPlay/Android Auto) несёт свои медиа+звонок — её аудио идёт ролями
projection/phone_call; контроль/протокол проекции — G. H лишь маршрутизирует звук по политике.
7. Радио, стриминг, плагин-источники (later)
- FM-радио (+ RDS): 🟡 требует тюнера, которого нет в hardware §4 → добавить FM-тюнер в периферию или отказаться от радио (§15 → hardware). DAB для РФ — вне скоупа. RDS-station/TMC — задел (TMC мог бы кормить нав I). Фаза v3.
- Стриминг: провайдер-агностично (#8), бэкенды сменные; креды — свои у пользователя (как LLM в
D §5). RU-сервисы (Yandex Music/VK/Звук). DRM/ToS некоторых сервисов — open (§15). Сеть — через
network-capability (plugin-sdk §3, host-allowlist). Underrun/исчерпание буфера →PlaybackState=buffering(§11) с rebuffer + таймаут → стоп с индикацией (ср. online-degradation D §5). Фаза v3. - Интернет-радио (HTTP/Icecast-потоки) — лёгкий вход, только
network(Opus-потоки →audiopus, §5). Фаза v3. - Источники-как-плагины (#9): H даёт точку расширения «media-source» — плагин регистрирует источник
звука (стриминг/подкасты/интернет-радио), как J — 0..N видео-источников.
audio_out+network; подчиняется intra-role media-фокусу (§3). → F/plugin-sdk.
8. Вывод: громкость, маршрутизация, усилитель
- Выход: PipeWire → I2S codec → усилитель (hardware §4). Одна зона салона (MVP); мульти-зона (задние) — вне скоупа/later (§15).
- Громкость: master + per-stream; mute; анти-clipping лимитер на миксе. 🟡 Кривая — перцептивная (логарифмическая) дефолт (линейная даёт неюзабельную ручку в шуме салона); реализуется в PipeWire-graph/WirePlumber (→ tech-stack). Balance/fader (L/R) и эквалайзер — вне скоупа MVP (одна зона), задел later (§15).
- Два тракта команды громкости (разные каналы, одно действие): (1) физические кнопки мультируля приходят как key-события через uinput → libinput → Wayland-input сфокусированному Media-клиенту (C §9, ADC-лесенка/проксированный CAN — K §3) — это НЕ интент ассистента; (2) голосовое «громче/тише» — локальный интент D §6 (после STT, без LLM).
- Pop-suppression: усилитель mute при boot/shutdown (anti-pop); enable/mute-секвенс amp — владелец физической линии hardware §4 (mute-GPIO), владелец порядка B §4 (mute-then-cut в teardown). При power-loss усилитель сбрасывается load-shedding'ом (hardware §3) — H обязан корректно отработать исчезновение output-ноды (без зависания, §3 sink-ось), не пытаться «доиграть».
9. Медиа и lifecycle (шов с B)
- Boot аудио-плоскости: PipeWire+WirePlumber и статическая лестница ролей (§3) поднимаются на Stage 1
(раньше Media-апа; сам Media — Stage 2, architecture §6), чтобы маршрутизация/политика были доступны до
прогрева плеера. До Stage 1 звука нет (как голоса нет в boot-окне, D §8). Роль
alertдоступна с поднятия WirePlumber (Stage 1); ранний park-beep на Stage-0-реверсе (до аудио-плоскости) — за её пределами, потребует отдельного раннего звукового пути (◻️ §15 → J/E/B; park-beep informational, #1). ShutdownImminent/ACC-off (ipc §3, B §4): остановить воспроизведение, mute amp до PONR (порядок — B §4, §8), сохранить now-playing (источник/трек/позиция/очередь) в Settings.- Resume после рестарта (later): восстановить контекст без авто-старта, если источник недоступен
(BT ещё не переподключился §6, USB вынут, нет сети §7) — показать now-playing как
paused/unavailable(§11) и ждать появления источника; авто-play — только когда источник готов; не висеть вbuffering. - Sleep: аудио выключено; медиа не держит систему бодрствующей вопреки power-политике (#5, B).
Воспроизведение — в
running/accessory, не вsleep/battery_cutoff. - Память (a-base §8): буферы декода/стриминга малы vs видео; медиа — throttleable, низкий приоритет OOM.
10. Driver-distraction (#6, владелец — C)
- На скорости выше порога: now-playing упрощается, просмотр библиотеки/набор текста — блокируются/ сворачиваются; приоритет — голосовому управлению (D). Правило — единое через Shell/SDK (C §7), H подчиняется.
11. IPC / интерфейсы
ru.shturman.Media(ipc §4, домен H):Play()/Pause()/Next()/Prev()/Seek(pos),SetSource(id),ListSources() → [(id, kind)](включая плагин-media-source §7, не только встроенный плеер); сигналNowPlayingChanged; propertiesPlaybackState(stopped/playing/paused/buffering/unavailable— источник пропал/не готов),NowPlaying(метаданные),Volume.- Аудио-политика: наружу — состояние (
AudioFocusChanged(role)/ propertyActiveFocus,DuckingActive) для UI; запрос фокуса — в основном неявно (роль потока + WirePlumber). Явная D-Bus-форма focus-request — ◻️ при необходимости (§15). - Гейтинг: воспроизведение плагином —
audio_out(security-privacy §3: только playback-ноды, не capture/monitor чужих); микрофон/loopback-монитор — никогда стороннему поaudio_out(этоaudio_in). - UI: now-playing — декларативный тайл (plugin-sdk §4.1) + богатый медиа-экран как Wayland-поверхность в слоте shell (C §4, slot-протокол).
12. Dev-симулятор (#13)
- Fake-аудио-источники поверх PipeWire (dev-environment): синтетический медиа-поток, мок BT-A2DP-ноды,
мок нав-промпта/TTS/звонка для матрицы ducking (§3); мок исчезновения output-ноды (load-shed / sink-ось
restore); отказ-сценарии §5/§6/§7 (битый файл, обрыв A2DP, ENOSPC, stream-underrun); дребезг
StateChanged(гистерезис §3). Сценарии «медиа↔звонок↔ассистент» и «два media-источника» (e2e) — отсюда. → dev-environment.
13. Функции
| функция | MVP/later | зависит от | фаза |
|---|---|---|---|
Аудио-плоскость + роли (PipeWire media.role, Stage 1) |
MVP | PipeWire/WirePlumber | v1 |
| Политика фокуса/ducking (лестница, crash-safe 2 оси, гистерезис) | MVP | WirePlumber, D (state) | v1 |
AEC (module-echo-cancel) + loopback-референс |
MVP | PipeWire, D | v1 |
| Громкость/mute (master + per-stream, кривая, мультируль) | MVP | C/K (ввод) | v1 |
| Вывод → codec/усилитель + pop-suppression | MVP | hardware, B | v1 |
| Локальный плеер (библиотека + декодер + отказ-пути) | MVP | storage, symphonia/audiopus |
v2 |
| Now-playing UI + транспорт (тайл + поверхность) | MVP | C (слоты), Shell | v2 |
| BT A2DP sink (SBC/AAC) + AVRCP (+ обрыв-handling) | MVP | BT-адаптер (G/Connectivity) | v2 |
| Resume playback после рестарта (без авто-старта) | later | Settings, B | v2 |
| Gapless-воспроизведение | later | symphonia |
v2 |
| FM-радио (+ RDS) | later | тюнер (нет в hardware §4) | v3 |
| Стриминг (провайдер-агностик, креды юзера) | later | network, plugin-sdk | v3 |
| Интернет-радио (HTTP/Icecast) | later | network | v3 |
| Media-source точка расширения (плагины) | later | plugin-sdk, F | v3 |
| Мульти-зона / balance-fader-EQ / BT A2DP source | later | hardware/BT | later |
14. Зависимости
- Вниз: hardware (§4 codec/усилитель/mic/тюнер/amp-mute-GPIO, §3 load-shedding), PipeWire/WirePlumber +
module-echo-cancel(tech-stack), storage (/data, библиотека),symphonia/audiopus/WebRTC-APM (tech-stack). - Вбок: D (роли
assistant_tts/capture, AEC-потребитель,StateChanged), G (HFP/BT-паринг/ проекция — звонок корчит медиа), I (нав-промпты, рольnav_guidance), B (ShutdownImminent/sleep/ACC, load-shed, amp-mute-порядок), C (мультируль-ввод, слоты now-playing, distraction-owner), J/E/K (служебныеalert). - Вверх: медиа-UI и состояние фокуса для Shell; расширяемо плагинами (
audio_out+ media-source).
15. Открытые вопросы
- 🟡 Декодер/кодеки:
symphonia(рек.) + AAC-патент (роялти) — юр-проверка; Opus →audiopus; SBC/AAC по BT, aptX/LDAC исключены (#12). → tech-stack. - 🟡 FM-тюнер: добавить в hardware §4 периферию или отказаться от радио. → hardware.
- ✅ Владелец BlueZ/паринга — Connectivity-core (резолв в G §3); сосуществование A2DP↔HFP — политика §3. → G.
- 🟡 enable/mute-секвенс усилителя (anti-pop): owner GPIO — hardware §4 (mute-GPIO), owner порядка — B §4. → hardware + B.
- ✅ AEC — узел audio-plane (
module-echo-cancel/WebRTC APM), референс = output-monitor, не в ассистенте; loopback-tap — §4. → резолв §4 (синхронизировано в d-assistant §10). - ◻️ Ранний звук на Stage-0-реверсе (park-beep до поднятия аудио-плоскости) — нужен ли отдельный путь. → J/E/B.
- ◻️ Split политики: сколько в статическом WirePlumber vs Rust-координаторе (+ crash-safety проактивного cork, тайминги гистерезиса/фейда дакинга). → реализация.
- ◻️ Явный focus-request API (D-Bus) vs только неявные роли. → реализация/ipc §4.
- ◻️ Gapless/crossfade — включаем ли в локальный плеер (предзагрузка трека). → реализация (v2).
- ◻️ Стриминг: какие RU-сервисы, DRM/ToS, официально vs плагин. → later.
- ◻️ Генерация служебных звуков (park-beep/предупреждения): ассеты и владелец синтеза (J/E vs общий chime-сервис H). → J/E + H.
- ◻️ Мульти-зона (задние) + balance/fader + EQ — подтвердить «вне скоупа MVP». → later/hardware.
Журнал решений (домен H)
| Решение | Выбор | Дата |
|---|---|---|
| Роль H | медиаплеер + design-owner аудио-политики; enforcement в WirePlumber (не в плеере) | 2026-06-22 |
| Модель арбитража | media.role-теги (playback) + лестница; media.category (Playback/Capture) — не арбитраж; intra-role media — единственный фокус |
2026-06-22 |
| Ducking | cork (звонок) vs duck (ассистент/нав); duck — относительный аттенюатор; гистерезис/фейд (анти-pumping) | 2026-06-22 |
| Crash-safe | две оси: source (жизнь ноды + проактивный cork по NameOwnerChanged/watchdog) + sink (пересчёт при возврате output) |
2026-06-22 |
| Где политика | статика — конфиг WirePlumber (Stage 1, переживает крэш); динамика — координатор Media-апа | 2026-06-22 |
| AEC | module-echo-cancel (WebRTC APM) в audio-plane (не в ассистенте); H даёт loopback-референс — резолв D §10 |
2026-06-22 |
| Декодер | symphonia (Rust, MPL-2.0; AAC-патент на юр-проверку; Opus НЕ поддержан → audiopus); aptX/LDAC исключены (#12) |
2026-06-22 |
| BT-аудио | A2DP sink (SBC/AAC) + AVRCP; обрыв → cork+auto-resume; паринг/HFP — G/Connectivity (шов) | 2026-06-22 |
| Радио/стриминг | later (v3); FM требует тюнера (нет в hardware); стриминг — креды юзера, провайдер-агностик | 2026-06-22 |
| Lifecycle | boot аудио-плоскости Stage 1; стоп+mute-amp на ShutdownImminent; resume без авто-старта; не держит wake | 2026-06-22 |