diff --git a/docs/architecture.md b/docs/architecture.md index d5b0048..842123f 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -207,7 +207,7 @@ Shell — Wayland-хост; апы/плагины — отдельные пес - **Разрешения и приватность** (детали брокера, 152-ФЗ) → [contracts/security-privacy.md](contracts/security-privacy.md). - **Железо и HAL/board-support** (питание, периферия, портирование) → [contracts/hardware.md](contracts/hardware.md). - **Камеры как 0..N источников, dashcam/surround; парктроники** → [domains/README.md](domains/README.md) (заделы) → домены J/K. -- **Аудио-арбитраж (ducking/приоритеты)** → домены Media/Assistant (политика поверх PipeWire). +- **Аудио-арбитраж (ducking/приоритеты)** → домен **H** (Media — design-owner политики) + D (Assistant); enforcement в WirePlumber. См. [domains/h-media-audio.md](domains/h-media-audio.md) §3. - **Ранний путь задней камеры (Stage 0)** → роадмап (объём работ) + домен J. --- diff --git a/docs/contracts/hardware.md b/docs/contracts/hardware.md index cb3527d..6647c5e 100644 --- a/docs/contracts/hardware.md +++ b/docs/contracts/hardware.md @@ -83,8 +83,9 @@ fail-safe при отказе/зависании самого MCU** (незав | **CAN/OBD** | ELM327 (USB/BT) | нативный CAN-трансивер → **SocketCAN** | | **GPS** | USB/UART, NMEA | **внешняя/активная антенна** (LNA); 1PPS — опц. (PPS-точность времени; на USB-донглах обычно нет) | | **Связь** | USB-модем (ModemManager) / Wi-Fi | — | -| **Аудио** | I2S codec + усилитель | — | +| **Аудио** | I2S codec + усилитель (+ mute/enable-GPIO — anti-pop) | — | | **Микрофон** | USB **mic-массив** (wake-word, шумоподавление) | — | +| **Радио** *(later)* | — | FM-тюнер (Si47xx-класс) — нужен для радио (домен H §7); 🟡 добавить или отказаться | | **Камеры** | задняя (CVBS capture-чип + драйвер, **v2**) | dashcam / surround (задел, домен J) | | **Мультируль** | кнопки руля: чтение с CAN **или** ADC (резистивная лесенка) | — | @@ -155,6 +156,8 @@ DBC/маппинг сигналов** (см. [data-model.md](data-model.md) §7) - 🟡 **Тепловой режим — софт закрыт** в [a-base](../domains/a-base-system.md) §10; **hardware-сторона открыта** (конверт/темп-класс/cold-start/охлаждение — §1a). - ◻️ **Бюджет hold-up** (числа: ток/hold-time/дератинг) — выбор supercap/cap гейтится этим (§3). - ◻️ **CAN-front-end части** (трансивер/защита) + **battery-RTC** на reference-плате — финализировать при выборе платы. +- 🟡 **FM-тюнер** (Si47xx-класс) для радио — добавить в периферию (§4) или отказаться от радио. → домен H §7. +- ◻️ **Amp mute/enable-GPIO** (anti-pop при boot/shutdown): линия + секвенс-контракт (mute-then-cut). → домен H §8 + B §4. - ✅ **Износ eMMC/SD** → резолв в [a-base-system.md](../domains/a-base-system.md) §9–§10 (journald volatile, tmpfs, zram вместо swap, write-minimization). --- diff --git a/docs/domains/README.md b/docs/domains/README.md index 4067d10..154a572 100644 --- a/docs/domains/README.md +++ b/docs/domains/README.md @@ -24,7 +24,7 @@ | E | Vehicle Data (OBD/CAN, read-only) | `e-vehicle-data.md` | PIDs, DTC + расшифровка, поездки, расход, VSS-модель | | F | Plugin host & экосистема | `f-plugin-host.md` | загрузка/sandbox/lifecycle плагинов, дистрибуция, dev-tools (API — в `../contracts/plugin-sdk.md`) | | G | Связь / телефон | `g-connectivity-phone.md` | BT HFP, модем/LTE, WiFi/hotspot, проекция телефона | -| H | Медиа / аудио | `h-media-audio.md` | **вся стандартная мультимедиа**: локальное аудио, BT A2DP/AVRCP, радио, стриминг | +| H | Медиа / аудио | `h-media-audio.md` | **вся стандартная мультимедиа** (локальное/BT A2DP/AVRCP/радио/стриминг) + **арбитр аудио** (политика фокуса/ducking поверх PipeWire) | | I | Навигация | `i-navigation.md` | офлайн-карты OSM, роутинг, POI, связка с ассистентом | | J | Камеры / видео | `j-cameras-video.md` | задняя камера, парктроник-оверлей, dashcam | | K | Датчики / периферия | `k-sensors-peripherals.md` | GPS/Location, IMU; кнопки руля — резистивная ADC-лесенка (CAN-кнопки/TPMS/климат/парктроник на CAN — у E); выделенные не-CAN датчики | diff --git a/docs/domains/b-power-lifecycle.md b/docs/domains/b-power-lifecycle.md index d384750..c1cd16a 100644 --- a/docs/domains/b-power-lifecycle.md +++ b/docs/domains/b-power-lifecycle.md @@ -67,6 +67,7 @@ ACC-off **больше** worst-case cold-crank-просадки; **гистер 2. **Load-shedding** (политика по триггеру): - *резкий обрыв/hold-up:* агрессивно сбросить всё, кроме SoC+хранилище; - *управляемый ACC-off:* мягко, с коротким grace-окном на дисплей/камеру-рейл (парковка с реверсом — §7, B-COMPL-04). + - **amp-mute перед снятием рейла усилителя** (anti-pop): mute-GPIO усилителя (hardware §4) дёргается ДО cut — шов с медиа-выходом H §8. 3. **Ordered teardown:** App-Host останавливает апы/плагины (`StopApp`) с per-app ack/save-таймаутом (сумма ≤ hold-up-бюджет); **E на `ShutdownImminent` гасит активный OBD-TX и закрывает ISO-TP-сессии** (B даёт сигнал, E владеет закрытием — B не трогает CAN). diff --git a/docs/domains/d-assistant.md b/docs/domains/d-assistant.md index b3f2eea..2958e87 100644 --- a/docs/domains/d-assistant.md +++ b/docs/domains/d-assistant.md @@ -142,9 +142,10 @@ intent router · LLM backend (pluggable) · TTS (Silero, офлайн). - ◻️ **Barge-in** (прерывание TTS) — later. - ◻️ **Размер контекста:** машина + память + история → токены (особенно у офлайн-модели с маленьким окном); нужна обрезка/суммаризация. → реализация. -- ◻️ **AEC (эхоподавление):** подавление собственного TTS/медиа-выхода (loopback-ref из - PipeWire) — нужно для wake-word во время воспроизведения и barge-in; место (WirePlumber/ - filter-chain vs внутри ассистента). → этот домен + architecture (audio plane). +- ✅ **AEC (эхоподавление):** подавление собственного TTS/медиа-выхода (loopback-ref из + PipeWire) — нужно для wake-word во время воспроизведения и barge-in. **Резолв:** узел audio-plane + на базе `module-echo-cancel` (WebRTC APM), референс = monitor выхода, **не внутри ассистента** — + loopback-tap публикует Media (H §4); ассистент потребляет уже очищенную capture-ноду. - ◻️ **Beamforming/денойз** (внешний шум салона): mic-массив; офлайн-STT фиксирован приватностью. → [hardware.md](../contracts/hardware.md). - ◻️ **Plugin-интенты как function-calling** (tool-use LLM) vs фразы — зависит от поддержки провайдером (у GigaChat/YandexGPT может быть ограничена). → реализация. diff --git a/docs/domains/h-media-audio.md b/docs/domains/h-media-audio.md new file mode 100644 index 0000000..fa825a7 --- /dev/null +++ b/docs/domains/h-media-audio.md @@ -0,0 +1,254 @@ +# Домен H — Медиа / аудио + +> Вся стандартная мультимедиа (локальное аудио, BT A2DP/AVRCP, радио, стриминг) **и** +> сквозной **арбитр аудио** — политика фокуса/ducking поверх PipeWire, которой подчиняются +> ассистент, телефон, навигация и служебные звуки. First-party ап на SDK (Rust). + +Статус: **v2 (на ревью).** v2 — после adversarial-ревью (20 находок). +Связано с: [architecture.md](../architecture.md) (§3 Media, §4 data-plane, §5 карта связей, §9 аудио-арбитраж) · [ipc.md](../contracts/ipc.md) (`Media`, `Assistant`, `Phone`, `Power`) · [security-privacy.md](../contracts/security-privacy.md) (§3 `audio_out`/`audio_in`, §4 гейтинг) · [plugin-sdk.md](../contracts/plugin-sdk.md) (§3 `network`, §4 точки расширения) · [hardware.md](../contracts/hardware.md) (§4 codec/усилитель/mic/тюнер/amp-mute, §3 load-shedding) · [tech-stack.md](../tech-stack.md) (PipeWire/WirePlumber, `symphonia`) · домены **D** (TTS/AEC), **G** (HFP/BT/проекция), **I** (нав-промпты), **C** (мультируль/слоты/distraction), **B** (lifecycle), **J/E/K** (служебные звуки) · [principles.md](../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-апа (§11 `SetSource`/состояние фокуса). (Ср. арбитраж 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) + политика **пересчитывается заново** по живым ролям-продюсерам, а не наследует залипшее состояние. +- **Где живёт:** *статическая* лестница ролей — **конфиг WirePlumber** в базовом образе (поднимается на + **Stage 1**, §9; переживает крэш плеера); *динамическая* координация (проактивный cork, гистерезис, + intra-role, состояние для UI) — компонент Media-апа, подписан на `Assistant.StateChanged`/`Phone`/`Nav`/ + `Power` (ipc §3–4). До Media-апа (v1) хватает статической политики + ролей ассистента. + +## 4. AEC и loopback-референс *(резолв D §147)* + +- 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 §147 в пользу audio-plane. +- Beamforming/денойз салона (D §148) — отдельно (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) — отдельный биндинг libopus **`audiopus`** (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 — ◻️ (§15, с G). +- **Проекция (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`; properties `PlaybackState` (`stopped`/`playing`/`paused`/`buffering`/ + **`unavailable`** — источник пропал/не готов), `NowPlaying` (метаданные), `Volume`. +- **Аудио-политика:** наружу — **состояние** (`AudioFocusChanged(role)` / property `ActiveFocus`, + `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 vs G vs shared) + сосуществование A2DP↔HFP. → G/Connectivity. +- 🟡 **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 §147; синхронизировано в 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 §147** | 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 | diff --git a/docs/tech-stack.md b/docs/tech-stack.md index 01e64c5..5cfce17 100644 --- a/docs/tech-stack.md +++ b/docs/tech-stack.md @@ -37,7 +37,7 @@ | **Языки (dev)** | Python | прототипы, Vehicle Simulator, скрипты — не в проде | | **UI** | **Slint** | декларативный, GPU-ускоренный, Rust-native | | **Графика** | **Wayland** — shell = кастомный композитор на `smithay` (Rust) · Panfrost/Mesa | `cage` не годится (single-app) — только ранний bring-up | -| **Аудио/видео plane** | **PipeWire** + WirePlumber | микрофон, TTS, медиа, BT-аудио | +| **Аудио/видео plane** | **PipeWire** + WirePlumber (+ `module-echo-cancel` — AEC) | микрофон, TTS, медиа, BT-аудио; политика ducking — домен H | | **IPC (control plane)** | **D-Bus** | низкополосное управление и события | | **Песочница** | **bubblewrap** + systemd-hardening | апы/плагины; WASM-тир — позже | | **OS base** | Armbian/Debian (RK3588), ядро ближе к mainline | read-only rootfs + overlay | @@ -71,6 +71,7 @@ | HTTP (облачные LLM) | `reqwest` | | Сериализация | `serde` | | Логи/трейсинг | `tracing` | +| Аудио-декод (медиа) | `symphonia` (MP3/AAC/FLAC/Vorbis/WAV/ALAC) + `audiopus` (Opus) | > Список крейтов уточняется при проектировании доменов; здесь — опорные.