Files
shturman/docs/domains/h-media-audio.md
kk0t9 3fd9b42bb0 docs(tails): закрыть кросс-док хвосты (a-base/b/dev-env/J/hardware) + наполнить glossary
Закрыты накопленные мелкие хвосты из ревью F/J/H/G:
- Stage-нормализация: Stage-0/1/2 → Stage 0/1/2 по 6 докам (a/b/f/h/j/hardware); каноническая запись Stage 0/1/2 в glossary.
- a-base §8: видео-пайплайн (DMABUF камер / VPU dashcam) внесён в OOM-порядок — задняя защищена (Stage 1), dashcam/surround throttleable.
- a-base §12: dashcam-медиа (отдельный носитель) + контакты/журнал (G) в список factory-reset wipe.
- b §12: grace-hold резолвнут  — J запросчик (J §7), B владелец/арбитр (§4 шаг 2, §7).
- dev-environment: моки fake-камера (J)/аудио (H)/BT-телефон (G) + plugin-host-харнесс; just-цели plugin-dev-run/sideload.
- J §3/§11/§13 + hardware §4: сигнал реверса  GPIO фонаря з.х. (выбранный дефолт); CAN-gear отложен (нет gear-сигнала в E).
- glossary.md: наполнен (~55 терминов в 7 областях: машина/CAN, платформа/IPC, ассистент/аудио, питание/boot, хранилище/OTA, связь/телефон, безопасность).

Tier-3 capability-catalog + roadmap не трогаются — зависят от доменов I+L.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 12:46:36 +03:00

28 KiB
Raw Permalink Blame History

Домен 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-апа (§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 §34). До 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) — отдельный биндинг 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-адаптера/паринга — 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; 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-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