diff --git a/.github/workflows/site.yml b/.github/workflows/site.yml index 1659393..ee294cf 100644 --- a/.github/workflows/site.yml +++ b/.github/workflows/site.yml @@ -29,6 +29,8 @@ jobs: run: | node tools/device-acceptance/validate-report.mjs \ tools/device-acceptance/fixtures/report-template-v1.json + node tools/device-acceptance/validate-report.mjs \ + tools/device-acceptance/fixtures/android-speaker-microphone-template-v1.json node tools/device-acceptance/test-validator.mjs - name: Verify English and Russian document pairs run: | @@ -45,11 +47,13 @@ jobs: "docs/research/acoustic-2-measurement-sources.md:docs/research/acoustic-2-measurement-sources_RU.md" "docs/research/device-acceptance-sources.md:docs/research/device-acceptance-sources_RU.md" "docs/research/live-audio-adapter-sources.md:docs/research/live-audio-adapter-sources_RU.md" + "docs/research/android-live-audio-adapter-sources.md:docs/research/android-live-audio-adapter-sources_RU.md" "docs/operations/device-acceptance.md:docs/operations/device-acceptance_RU.md" "spec/protocol-v1.md:spec/protocol-v1_RU.md" "spec/acoustic-1.md:spec/acoustic-1_RU.md" "spec/acoustic-2.md:spec/acoustic-2_RU.md" "spec/live-audio-adapter-v1.md:spec/live-audio-adapter-v1_RU.md" + "spec/android-live-audio-adapter-v1.md:spec/android-live-audio-adapter-v1_RU.md" ) for pair in "${pairs[@]}"; do source_path="${pair%%:*}" diff --git a/docs/README.md b/docs/README.md index 59ceadb..701101f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -20,10 +20,12 @@ This directory holds the human-readable technical documentation for AudioModem. | Research | [Acoustic-2 measurement sources](research/acoustic-2-measurement-sources.md) | [Источники измерений Acoustic-2](research/acoustic-2-measurement-sources_RU.md) | Controlled PCM transform and timing-acquisition constraints | | Research | [Device-acceptance sources](research/device-acceptance-sources.md) | [Источники device acceptance](research/device-acceptance-sources_RU.md) | Reproducible evidence, metadata and privacy constraints | | Research | [Live-audio adapter sources](research/live-audio-adapter-sources.md) | [Источники live-audio adapter](research/live-audio-adapter-sources_RU.md) | Session, permission, focus and lifecycle constraints | +| Research | [Android live-audio adapter sources](research/android-live-audio-adapter-sources.md) | [Источники Android live-audio adapter](research/android-live-audio-adapter-sources_RU.md) | First-target decision and Android/PipeWire route constraints | | Specification | [ADLP v1](../spec/protocol-v1.md) | [ADLP v1 на русском](../spec/protocol-v1_RU.md) | Normative wire object and WAV bootstrap carrier | | Specification | [Acoustic-1](../spec/acoustic-1.md) | [Acoustic-1 на русском](../spec/acoustic-1_RU.md) | Experimental B-FSK carrier and compatibility boundary | | Specification | [Acoustic-2](../spec/acoustic-2.md) | [Acoustic-2 на русском](../spec/acoustic-2_RU.md) | Experimental controlled PCM measurement contract | | Specification | [Live-audio adapter v1](../spec/live-audio-adapter-v1.md) | [Live-audio adapter v1 на русском](../spec/live-audio-adapter-v1_RU.md) | Typed unavailable-first contract for future routes | +| Specification | [Android live-audio adapter v1](../spec/android-live-audio-adapter-v1.md) | [Android live-audio adapter v1 на русском](../spec/android-live-audio-adapter-v1_RU.md) | Draft foreground Android route lifecycle and acceptance constraints | ## Translation convention diff --git a/docs/README_RU.md b/docs/README_RU.md index a2daae0..522e1ee 100644 --- a/docs/README_RU.md +++ b/docs/README_RU.md @@ -22,10 +22,12 @@ | Research | [Acoustic-2 measurement sources](research/acoustic-2-measurement-sources.md) | [Источники измерений Acoustic-2](research/acoustic-2-measurement-sources_RU.md) | Ограничения controlled PCM transforms и timing acquisition | | Research | [Device-acceptance sources](research/device-acceptance-sources.md) | [Источники device acceptance](research/device-acceptance-sources_RU.md) | Ограничения reproducible evidence, metadata и privacy | | Research | [Live-audio adapter sources](research/live-audio-adapter-sources.md) | [Источники live-audio adapter](research/live-audio-adapter-sources_RU.md) | Ограничения session, permission, focus и lifecycle | +| Research | [Android live-audio adapter sources](research/android-live-audio-adapter-sources.md) | [Источники Android live-audio adapter](research/android-live-audio-adapter-sources_RU.md) | Решение о first target и Android/PipeWire route constraints | | Specification | [ADLP v1](../spec/protocol-v1.md) | [ADLP v1 на русском](../spec/protocol-v1_RU.md) | Нормативный wire object и WAV bootstrap carrier | | Specification | [Acoustic-1](../spec/acoustic-1.md) | [Acoustic-1 на русском](../spec/acoustic-1_RU.md) | Экспериментальный B-FSK carrier и граница compatibility | | Specification | [Acoustic-2](../spec/acoustic-2.md) | [Acoustic-2 на русском](../spec/acoustic-2_RU.md) | Экспериментальный controlled PCM measurement contract | | Specification | [Live-audio adapter v1](../spec/live-audio-adapter-v1.md) | [Live-audio adapter v1 на русском](../spec/live-audio-adapter-v1_RU.md) | Typed unavailable-first contract для будущих routes | +| Specification | [Android live-audio adapter v1](../spec/android-live-audio-adapter-v1.md) | [Android live-audio adapter v1 на русском](../spec/android-live-audio-adapter-v1_RU.md) | Draft foreground Android route lifecycle и acceptance constraints | ## Правило поддержки перевода diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 3bc80c1..e5ca7f1 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -54,11 +54,11 @@ Text must not be silently overloaded to represent an image or file. The first bo ### Stage E — Add one live-audio platform adapter -Only after the Flutter shell and PCM contract are stable should work begin on one platform. The initial target must be selected explicitly in a route RFC; it will specify plugin/native APIs, session/permission policy, interruption behavior, one route, buffer boundaries, diagnostics and stop/dispose semantics. The adapter starts as experimental and defaults to unavailable outside its declared scope. +After the Flutter shell and PCM contract are stable, platform work begins with one route RFC. The first selected design target is **Android API 26+ foreground playback/capture**, documented in the [Android live-audio adapter v1 RFC](../spec/android-live-audio-adapter-v1.md). It specifies native APIs, session/permission policy, interruption behavior, default-route boundary, buffer/format checks, diagnostics and stop/dispose semantics. The adapter remains unavailable until the RFC and acceptance change receive review; it will remain unavailable outside its declared scope. | Entry condition | Work product | Exit gate | Explicit non-claim | | --- | --- | --- | --- | -| Route RFC, test method and target build are approved. | One adapter, unit/integration tests and a privacy-preserving device-acceptance template. | The adapter reports only its declared availability; a reviewed physical-route report exists for the exact scope. | One observation is not a broad compatibility, range, Bluetooth or radio claim. | +| Android route RFC, machine-checked acceptance extension and target build are approved. | One Android adapter, unit/integration tests and real privacy-preserving physical-route reports. | The adapter reports only its declared availability; a reviewed physical-route report exists for the exact scope. | One observation is not a broad compatibility, range, Bluetooth or radio claim. | ### Stage F — Publish evidence, compatibility and releases @@ -76,4 +76,4 @@ After repeated reviewed evidence, compatibility documentation may describe the e ## Immediate execution order -The active implementation milestone is **Stage B preparation**. First, the project will define a small transfer-task model and fake bridge fixture for the existing text/WAV workflow. Next, the Flutter shell will be separated from its current page-local state so that widget tests can drive send, receive, cancellation, unavailable and rejection paths without native audio. The existing Rust bridge and golden fixtures stay unchanged until the shell contract identifies a concrete missing typed value. +The active milestone is **Stage E Android route design review**. The repository now has a bilingual Android foreground RFC, research decision, unexecuted reporting template and validator extension that requires exact v1 PCM, permission, focus, stop and privacy fields for future Android speaker-to-microphone measurements. The next implementation change may add a native Android adapter only after this design slice is reviewed and merged. It must begin unavailable outside Android API 26+ foreground scope and must not create a physical-route claim without real reviewed observations. diff --git a/docs/implementation-plan_RU.md b/docs/implementation-plan_RU.md index f6e2ec5..60b77fc 100644 --- a/docs/implementation-plan_RU.md +++ b/docs/implementation-plan_RU.md @@ -56,11 +56,11 @@ Text не должен молча использоваться как image ил ### Stage E — Добавить один live-audio platform adapter -Только после стабилизации Flutter shell и PCM contract начинается работа на одной platform. Initial target выбирается явно в route RFC; он задаёт plugin/native APIs, session/permission policy, interruption behavior, один route, buffer boundaries, diagnostics и stop/dispose semantics. Adapter начинается как experimental и defaults to unavailable вне declared scope. +После стабилизации Flutter shell и PCM contract platform work начинается с одного route RFC. Первый selected design target — **Android API 26+ foreground playback/capture**, описанный в [Android live-audio adapter v1 RFC](../spec/android-live-audio-adapter-v1_RU.md). Он задаёт native APIs, session/permission policy, interruption behavior, default-route boundary, buffer/format checks, diagnostics и stop/dispose semantics. Adapter остаётся unavailable до review RFC и acceptance change; вне declared scope он останется unavailable. | Entry condition | Work product | Exit gate | Explicit non-claim | | --- | --- | --- | --- | -| Route RFC, test method и target build утверждены. | Один adapter, unit/integration tests и privacy-preserving device-acceptance template. | Adapter сообщает только declared availability; существует reviewed physical-route report для exact scope. | Одно observation не является broad compatibility, range, Bluetooth или radio claim. | +| Android route RFC, machine-checked acceptance extension и target build утверждены. | Один Android adapter, unit/integration tests и real privacy-preserving physical-route reports. | Adapter сообщает только declared availability; существует reviewed physical-route report для exact scope. | Одно observation не является broad compatibility, range, Bluetooth или radio claim. | ### Stage F — Публикация evidence, compatibility и releases @@ -78,4 +78,4 @@ Text не должен молча использоваться как image ил ## Ближайший порядок исполнения -Активный implementation milestone — **подготовка Stage B**. Сначала проект определит small transfer-task model и fake bridge fixture для existing text/WAV workflow. Затем Flutter shell будет отделена от текущего page-local state, чтобы widget tests могли управлять send, receive, cancellation, unavailable и rejection paths без native audio. Existing Rust bridge и golden fixtures остаются без изменений, пока shell contract не выявит конкретное отсутствующее typed value. +Активный milestone — **Stage E Android route design review**. В repository теперь есть bilingual Android foreground RFC, research decision, unexecuted reporting template и validator extension, требующий exact v1 PCM, permission, focus, stop и privacy fields для future Android speaker-to-microphone measurements. Next implementation change может добавить native Android adapter только после review и merge этого design slice. Он должен начинаться unavailable вне Android API 26+ foreground scope и не может создавать physical-route claim без real reviewed observations. diff --git a/docs/operations/device-acceptance.md b/docs/operations/device-acceptance.md index 92f699c..cab15a2 100644 --- a/docs/operations/device-acceptance.md +++ b/docs/operations/device-acceptance.md @@ -1,6 +1,6 @@ # Device-acceptance protocol -**Status:** Experimental operations contract · **Last reviewed:** 2026-08-20 · **English (canonical)** · [Русский](device-acceptance_RU.md) +**Status:** Experimental operations contract · **Last reviewed:** 2026-08-21 · **English (canonical)** · [Русский](device-acceptance_RU.md) This protocol governs future evidence for a live audio route. It does **not** make any platform, device, cable, Bluetooth path, radio interface or Acoustic-1/Acoustic-2 experiment supported. A single report is an observation awaiting review; it is not a performance claim. @@ -42,6 +42,20 @@ node tools/device-acceptance/validate-report.mjs \ The committed fixture above is intentionally an **unexecuted template**, not a device result. A contributor creates a new measurement sidecar only after obtaining real hardware observations. +## Android foreground v1 extension + +The [Android foreground live-audio adapter RFC](../../spec/android-live-audio-adapter-v1.md) selects the first experimental platform-route design. It does not enable a route. The generic schema and validator now reserve `adapter_observation` for a future `measurement` whose route is exactly `speaker_microphone` and whose source and sink platforms are both `android`. That record must declare the reviewed adapter contract, Android API level of at least 26, playback/capture operation, requested and effective **48 kHz / mono / PCM16 LE** format, granted microphone permission, granted or delayed-then-granted focus, route-change observation, stop-without-auto-resume policy and discarded raw PCM. + +This extension is deliberately narrow. It neither permits an unexecuted template to include device/evidence fields nor changes requirements for every other route. It rejects an Android observation with a mismatched effective format or any value that would imply raw audio retention or auto-resume. + +```bash +node tools/device-acceptance/validate-report.mjs \ + tools/device-acceptance/fixtures/android-speaker-microphone-template-v1.json +node tools/device-acceptance/test-validator.mjs +``` + +The Android template is still unexecuted. It contains no device, run, outcome, permission or route observation and must not be cited as an Android measurement. + ## Decision gates | Gate | Required evidence | Permitted label | diff --git a/docs/operations/device-acceptance_RU.md b/docs/operations/device-acceptance_RU.md index ed2beaa..dcd16b9 100644 --- a/docs/operations/device-acceptance_RU.md +++ b/docs/operations/device-acceptance_RU.md @@ -2,7 +2,7 @@ [English (canonical)](device-acceptance.md) · **Русский перевод** -> **Translation of:** [docs/operations/device-acceptance.md](device-acceptance.md). **Last synced:** 2026-08-20. Английский оригинал определяет contract implementation. +> **Translation of:** [docs/operations/device-acceptance.md](device-acceptance.md). **Last synced:** 2026-08-21. Английский оригинал определяет contract implementation. Этот protocol регулирует будущие evidence для live audio route. Он **не** делает supported ни платформу, ни устройство, ни кабель, ни Bluetooth path, ни radio interface, ни Acoustic-1/Acoustic-2 experiment. Один report — observation, ожидающее review, а не performance claim. @@ -44,6 +44,20 @@ node tools/device-acceptance/validate-report.mjs \ Committed fixture выше намеренно является **unexecuted template**, а не device result. Новый measurement sidecar создаётся только после получения реальных hardware observations. +## Android foreground v1 extension + +[Android foreground live-audio adapter RFC](../../spec/android-live-audio-adapter-v1_RU.md) выбирает first experimental platform-route design. Он не включает route. Generic schema и validator теперь резервируют `adapter_observation` для будущего `measurement`, где route точно `speaker_microphone`, а source и sink platforms — оба `android`. Такой record обязан указать reviewed adapter contract, Android API level не ниже 26, playback/capture operation, requested и effective **48 kHz / mono / PCM16 LE** format, granted microphone permission, granted или delayed-then-granted focus, route-change observation, stop-without-auto-resume policy и discarded raw PCM. + +Extension намеренно узкий. Он не разрешает unexecuted template включать device/evidence fields и не изменяет требования для остальных routes. Он отклоняет Android observation с mismatched effective format или любым value, предполагающим raw audio retention либо auto-resume. + +```bash +node tools/device-acceptance/validate-report.mjs \ + tools/device-acceptance/fixtures/android-speaker-microphone-template-v1.json +node tools/device-acceptance/test-validator.mjs +``` + +Android template остаётся unexecuted. В нём нет device, run, outcome, permission или route observation и его нельзя приводить как Android measurement. + ## Decision gates | Gate | Required evidence | Permitted label | diff --git a/docs/reference/platform-support.md b/docs/reference/platform-support.md index 8a8d670..9a0e3d0 100644 --- a/docs/reference/platform-support.md +++ b/docs/reference/platform-support.md @@ -1,6 +1,6 @@ # Platform support -**Last reviewed:** 2026-08-20 · **English (canonical)** · [Русский](platform-support_RU.md) +**Last reviewed:** 2026-08-21 · **English (canonical)** · [Русский](platform-support_RU.md) AudioModem keeps Flutter runners for Android, iOS, Windows, macOS, Linux and Web in one application directory. The Rust workspace supplies the container and WAV bootstrap codec. This structure is an implementation target, not a claim that every platform currently supports every route. @@ -8,7 +8,7 @@ AudioModem keeps Flutter runners for Android, iOS, Windows, macOS, Linux and Web | Platform | Flutter runner | Rust/WAV integration | Local WAV workflow | Live audio | Notes | | --- | --- | --- | --- | --- | --- | -| Android | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Planned | Requires target build, permission and route-adapter acceptance tests. | +| Android | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Draft experimental design | [Foreground adapter RFC](../../spec/android-live-audio-adapter-v1.md) and validator extension exist; target build, permission behavior, adapter and route evidence remain unverified. | | iOS | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Planned | Requires target build, permission and route-adapter acceptance tests. | | Windows | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Planned | Requires native device enumeration and routing tests. | | macOS | Scaffolded | Bridge source integrated; target build/run not verified | Source implementation; target dialog not verified | Planned | Requires native device enumeration and routing tests. | diff --git a/docs/reference/platform-support_RU.md b/docs/reference/platform-support_RU.md index 66bd0ee..841b4ba 100644 --- a/docs/reference/platform-support_RU.md +++ b/docs/reference/platform-support_RU.md @@ -2,7 +2,7 @@ [English (canonical)](platform-support.md) · **Русский перевод** -> **Translation of:** [docs/reference/platform-support.md](platform-support.md). **Last synced:** 2026-08-20. +> **Translation of:** [docs/reference/platform-support.md](platform-support.md). **Last synced:** 2026-08-21. AudioModem хранит Flutter runners для Android, iOS, Windows, macOS, Linux и Web в одном application directory. Rust workspace предоставляет container и WAV bootstrap codec. Эта структура — implementation target, а не утверждение, что каждая платформа уже поддерживает каждый route. @@ -10,7 +10,7 @@ AudioModem хранит Flutter runners для Android, iOS, Windows, macOS, Lin | Платформа | Flutter runner | Rust/WAV integration | Локальный WAV workflow | Live audio | Примечание | | --- | --- | --- | --- | --- | --- | -| Android | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Планируется | Нужны target build, permission и route-adapter acceptance tests. | +| Android | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Draft experimental design | Существуют [foreground adapter RFC](../../spec/android-live-audio-adapter-v1_RU.md) и validator extension; target build, permission behavior, adapter и route evidence остаются непроверенными. | | iOS | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Планируется | Нужны target build, permission и route-adapter acceptance tests. | | Windows | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Планируется | Нужны native device enumeration и routing tests. | | macOS | Scaffolded | Bridge source integrated; target build/run не проверен | Source implementation; target dialog не проверен | Планируется | Нужны native device enumeration и routing tests. | diff --git a/docs/research/android-live-audio-adapter-sources.md b/docs/research/android-live-audio-adapter-sources.md new file mode 100644 index 0000000..cee466b --- /dev/null +++ b/docs/research/android-live-audio-adapter-sources.md @@ -0,0 +1,38 @@ +# Android live-audio adapter research note + +**Status:** Informative research for an experimental route RFC. It is not a compatibility claim. + +## Decision + +The first platform-specific live-audio design target is **Android foreground playback and capture**. It fits the existing Flutter shell and lets the project exercise a permission-bearing mobile route without expanding into Linux session-manager policy. The design target is deliberately narrower than “Android support”: it will establish an adapter contract and a route-specific evidence method, not a general device matrix. + +| Consideration | Android first target | Linux desktop deferred target | +| --- | --- | --- | +| Platform surface | `AudioRecord` pulls capture frames and `AudioTrack` pushes PCM playback frames. [1] [2] | PipeWire has devices, nodes, ports, links and a session manager that configures and re-links them. [5] [6] | +| User-consent boundary | `RECORD_AUDIO` is a runtime permission and should be requested in the context of the action that needs it. [1] [3] | Device enablement, profiles, routes and access policy are distribution/session-manager concerns. [6] | +| Playback lifecycle | The app should obtain audio focus immediately before playback, react to focus loss, and abandon focus after playback ends. Android 15 adds a foreground/top-app condition for requesting focus. [4] | Dynamic sink/source changes and links are managed across PipeWire and a session manager. [5] [6] | +| First acceptance scope | A foreground, user-initiated, default-route experiment with explicit device observations. | A later route RFC must select backend, session-manager assumptions and a reproducible Linux environment. | + +## Constraints carried into the RFC + +`AudioRecord` requires `RECORD_AUDIO`; it records by application reads and reports initialization/operation errors. Its buffer needs to be read before it overruns. [1] The Android adapter must therefore create capture only after the foreground user action and granted permission, reject an uninitialized record, surface read errors, stop/release deterministically and never retain raw microphone PCM after the active attempt. + +`AudioTrack` accepts PCM through application writes and supports streaming mode. [2] The adapter should write the existing Rust-produced PCM in bounded chunks, stop/release deterministically, and report write or initialization failure rather than declaring a transmission completed. It must request focus immediately before user-initiated playback and cease playback on permanent or transient focus loss; delayed focus is a waiting state, not permission to start. [4] + +The existing `PcmStreamFormat.audioModemV1` remains the application intent: **48 kHz, mono, signed PCM16 little-endian**. Android API documentation guarantees neither that a requested 48 kHz capture configuration is available on every device nor that an actual route remains unchanged. The adapter must inspect the created format/routed device and enter a rejected or route-changed diagnostic state when it cannot preserve the v1 format. `setPreferredDevice()` is only a preference, so first implementation must not advertise cable, Bluetooth, headset or radio routing. [1] [2] + +The first adapter will not start a foreground service, operate while the app is backgrounded, record continuously, auto-resume after interruption, choose a Bluetooth route, perform duplex echo cancellation, or claim speaker-to-microphone delivery. Android permission guidance requires a feature to degrade gracefully after denial or revocation; the WAV workflow remains available in those states. [3] + +## Sources + +[1] [Android Developers: `AudioRecord` API reference](https://developer.android.com/reference/android/media/AudioRecord) + +[2] [Android Developers: `AudioTrack` API reference](https://developer.android.com/reference/android/media/AudioTrack) + +[3] [Android Developers: Request runtime permissions](https://developer.android.com/training/permissions/requesting) + +[4] [Android Developers: Manage audio focus](https://developer.android.com/media/optimize/audio-focus) + +[5] [PipeWire: Overview](https://docs.pipewire.org/devel/page_overview.html) + +[6] [WirePlumber: Understanding session management](https://pipewire.pages.freedesktop.org/wireplumber/design/understanding_session_management.html) diff --git a/docs/research/android-live-audio-adapter-sources_RU.md b/docs/research/android-live-audio-adapter-sources_RU.md new file mode 100644 index 0000000..6047005 --- /dev/null +++ b/docs/research/android-live-audio-adapter-sources_RU.md @@ -0,0 +1,42 @@ +# Research note: Android live-audio adapter + +[English (canonical)](android-live-audio-adapter-sources.md) · **Русский перевод** + +> **Translation of:** [docs/research/android-live-audio-adapter-sources.md](android-live-audio-adapter-sources.md). **Last synced:** 2026-08-21. + +**Статус:** Informative research для experimental route RFC. Это не compatibility claim. + +## Решение + +Первой platform-specific целью дизайна live audio выбран **Android foreground playback и capture**. Он соответствует существующему Flutter shell и позволяет проверить mobile route с permission, не расширяя работу до Linux session-manager policy. Цель намеренно уже, чем «Android support»: она фиксирует adapter contract и route-specific evidence method, а не general device matrix. + +| Фактор | Android first target | Linux desktop отложен | +| --- | --- | --- | +| Platform surface | `AudioRecord` pulls capture frames, а `AudioTrack` pushes PCM playback frames. [1] [2] | PipeWire использует devices, nodes, ports, links и session manager, который их конфигурирует и re-links. [5] [6] | +| Граница user consent | `RECORD_AUDIO` — runtime permission; его следует запрашивать в контексте действия, которому он нужен. [1] [3] | Device enablement, profiles, routes и access policy зависят от distribution/session-manager. [6] | +| Playback lifecycle | App получает audio focus непосредственно перед playback, реагирует на focus loss и abandon focus после конца playback. Android 15 добавляет условие foreground/top-app для запроса focus. [4] | Dynamic sink/source changes и links управляются PipeWire и session manager. [5] [6] | +| First acceptance scope | Foreground, user-initiated, default-route experiment с explicit device observations. | Later route RFC должен выбрать backend, session-manager assumptions и reproducible Linux environment. | + +## Ограничения, переносимые в RFC + +`AudioRecord` требует `RECORD_AUDIO`, записывает посредством application reads и сообщает initialization/operation errors. Его buffer должен считываться до overruns. [1] Поэтому Android adapter создаёт capture только после foreground user action и granted permission, отклоняет uninitialized record, показывает read errors, детерминированно stop/release и не хранит raw microphone PCM после active attempt. + +`AudioTrack` принимает PCM через application writes и поддерживает streaming mode. [2] Adapter записывает существующий Rust-produced PCM bounded chunks, детерминированно stop/release и сообщает write или initialization failure вместо заявления о completed transmission. Он запрашивает focus непосредственно перед user-initiated playback и прекращает playback при permanent или transient focus loss; delayed focus — waiting state, но не разрешение начать. [4] + +Существующий `PcmStreamFormat.audioModemV1` остаётся application intent: **48 kHz, mono, signed PCM16 little-endian**. Android documentation не гарантирует, что requested 48 kHz capture configuration доступен на каждом device, либо что actual route останется неизменным. Adapter обязан проверить created format/routed device и перейти в rejected или route-changed diagnostic state, если v1 format не сохраняется. `setPreferredDevice()` — только preference, поэтому first implementation не заявляет cable, Bluetooth, headset или radio routing. [1] [2] + +Первый adapter не запускает foreground service, не работает в background, не пишет continuously, не auto-resume после interruption, не выбирает Bluetooth route, не выполняет duplex echo cancellation и не заявляет speaker-to-microphone delivery. Android permission guidance требует graceful degradation после denial или revocation; WAV workflow остаётся доступным в этих состояниях. [3] + +## Источники + +[1] [Android Developers: `AudioRecord` API reference](https://developer.android.com/reference/android/media/AudioRecord) + +[2] [Android Developers: `AudioTrack` API reference](https://developer.android.com/reference/android/media/AudioTrack) + +[3] [Android Developers: Request runtime permissions](https://developer.android.com/training/permissions/requesting) + +[4] [Android Developers: Manage audio focus](https://developer.android.com/media/optimize/audio-focus) + +[5] [PipeWire: Overview](https://docs.pipewire.org/devel/page_overview.html) + +[6] [WirePlumber: Understanding session management](https://pipewire.pages.freedesktop.org/wireplumber/design/understanding_session_management.html) diff --git a/docs/roadmap.md b/docs/roadmap.md index f21bc99..fc456a8 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -15,6 +15,7 @@ This roadmap describes intended order, not dates or guaranteed delivery. It dist | Acoustic-2 | Experimental measurement harness | Declared integer PCM transforms, bounded acquisition observables and golden measurement contract exist around Acoustic-1; no device or channel metric claim. | | Device-acceptance infrastructure | Completed reporting/tooling contract | Schema, validator, privacy-preserving intake template and decision gates prepare future evidence; no physical-route observation or supported device claim exists. | | Live-audio adapter contract | Completed unavailable scaffold | Typed PCM/lifecycle boundary and unavailable behavior tests exist; no plugin, permission, capture, playback or physical route is enabled. | +| Android foreground live-audio design | Draft experimental route design | Android API 26+ RFC, research decision and route-observation validator extension exist; no Android adapter, permission flow, target build or physical-route result exists. | | Audio adapters | Planned | Capture/playback, cable, Bluetooth and radio-interface adapters with per-platform acceptance tests. | | Trust and encryption | Planned RFC | Key lifecycle, manual/QR exchange, authenticated encryption, verification UX and independent security review. | | Release engineering | Planned | Signed packages, compatibility matrix, changelog, SBOM/checksums and clear support policy. | diff --git a/docs/roadmap_RU.md b/docs/roadmap_RU.md index 71c3ddd..99ab94b 100644 --- a/docs/roadmap_RU.md +++ b/docs/roadmap_RU.md @@ -17,6 +17,7 @@ | Acoustic-2 | Экспериментальный measurement harness | Вокруг Acoustic-1 существуют declared integer PCM transforms, bounded acquisition observables и golden measurement contract; device или channel metric claim отсутствует. | | Device-acceptance infrastructure | Завершён reporting/tooling contract | Schema, validator, privacy-preserving intake template и decision gates готовят future evidence; physical-route observation или supported device claim отсутствуют. | | Live-audio adapter contract | Завершён unavailable scaffold | Существуют typed PCM/lifecycle boundary и unavailable behavior tests; plugin, permission, capture, playback или physical route не включены. | +| Android foreground live-audio design | Draft experimental route design | Существуют Android API 26+ RFC, research decision и route-observation validator extension; Android adapter, permission flow, target build или physical-route result отсутствуют. | | Audio adapters | Планируется | Capture/playback, cable, Bluetooth и radio-interface adapters с per-platform acceptance tests. | | Trust и encryption | Planned RFC | Key lifecycle, manual/QR exchange, authenticated encryption, verification UX и independent security review. | | Release engineering | Планируется | Signed packages, compatibility matrix, changelog, SBOM/checksums и clear support policy. | diff --git a/schemas/device-acceptance-report-v1.schema.json b/schemas/device-acceptance-report-v1.schema.json index fa18ede..06315e0 100644 --- a/schemas/device-acceptance-report-v1.schema.json +++ b/schemas/device-acceptance-report-v1.schema.json @@ -54,9 +54,36 @@ "summary": { "type": "string", "minLength": 1, "maxLength": 1000 } }, "additionalProperties": false + }, + "adapter_observation": { + "type": "object", + "required": ["adapter_contract", "api_level", "operation", "requested_format", "effective_format", "permission_state", "focus_outcome", "route_change_observed", "interruption_policy", "raw_pcm_retention"], + "properties": { + "adapter_contract": { "const": "android-live-audio-adapter/v1" }, + "api_level": { "type": "integer", "minimum": 26 }, + "operation": { "const": "playback_capture" }, + "requested_format": { "$ref": "#/$defs/audioModemV1Format" }, + "effective_format": { "$ref": "#/$defs/audioModemV1Format" }, + "permission_state": { "const": "granted" }, + "focus_outcome": { "enum": ["granted", "delayed_then_granted"] }, + "route_change_observed": { "type": "boolean" }, + "interruption_policy": { "const": "stop_no_auto_resume" }, + "raw_pcm_retention": { "const": "discarded" } + }, + "additionalProperties": false } }, "$defs": { + "audioModemV1Format": { + "type": "object", + "required": ["sample_rate_hz", "channels", "sample_format"], + "properties": { + "sample_rate_hz": { "const": 48000 }, + "channels": { "const": 1 }, + "sample_format": { "const": "pcm_s16le" } + }, + "additionalProperties": false + }, "endpoint": { "type": "object", "required": ["platform", "device_class"], @@ -76,7 +103,8 @@ { "required": ["device"] }, { "required": ["test"] }, { "required": ["evidence"] }, - { "required": ["outcome"] } + { "required": ["outcome"] }, + { "required": ["adapter_observation"] } ] } }, @@ -85,5 +113,24 @@ "required": ["device", "test", "evidence", "outcome"] } ], + "allOf": [ + { + "if": { + "properties": { + "report_type": { "const": "measurement" }, + "device": { + "properties": { + "route": { "const": "speaker_microphone" }, + "source": { "properties": { "platform": { "const": "android" } }, "required": ["platform"] }, + "sink": { "properties": { "platform": { "const": "android" } }, "required": ["platform"] } + }, + "required": ["route", "source", "sink"] + } + }, + "required": ["report_type", "device"] + }, + "then": { "required": ["adapter_observation"] } + } + ], "additionalProperties": false } diff --git a/site/index.html b/site/index.html index ef2c7e2..1af1d49 100644 --- a/site/index.html +++ b/site/index.html @@ -15,7 +15,7 @@

First test

Start with a reproducible WAV round trip.

WAV removes unknown live-audio characteristics. The native app can export a verified WAV and import it for Rust-side validation; Acoustic-1 has a controlled B-FSK carrier and Acoustic-2 measures declared PCM transforms, neither of which is a live-audio claim.

CLI / bootstrapcargo run -p adlp-cli -- encode-text hello.wav N1 "Hello" reliable
  cargo run -p adlp-cli -- decode hello.wav
Open the short guide →

Architecture

A small app over an independent Rust core.

FlutterUI, routes, file selection and diagnostics
→
Rust bridgeOne codec implementation for the client
→
ADLP + DSPContainer, WAV and future PHY profiles
Architecture and stack →
-

Status

Current slice—without promises beyond the implementation.

✓ ADLP v1 + CRC-32C
done
✓ Deterministic PCM/WAV bootstrap
done
✓ Flutter ↔ Rust bridge
native text/file-to-WAV verification
✓ Local WAV import / export
native dialogs + Rust validation
✓ Experimental file-object WAV
bounded local file selection, receipt and save; no live route
✓ Experimental Acoustic-1
B-FSK controlled PCM/WAV codec + golden vector
✓ Acoustic-2 measurement
declared PCM transforms + acquisition observables
✓ Device-acceptance protocol
evidence schema and review gates; no device result
✓ Live-audio adapter contract
typed unavailable scaffold; no capture, playback or route
• Live audio and encryption
planned
See roadmap →
+

Status

Current slice—without promises beyond the implementation.

✓ ADLP v1 + CRC-32C
done
✓ Deterministic PCM/WAV bootstrap
done
✓ Flutter ↔ Rust bridge
native text/file-to-WAV verification
✓ Local WAV import / export
native dialogs + Rust validation
✓ Experimental file-object WAV
bounded local file selection, receipt and save; no live route
✓ Experimental Acoustic-1
B-FSK controlled PCM/WAV codec + golden vector
✓ Acoustic-2 measurement
declared PCM transforms + acquisition observables
✓ Device-acceptance protocol
evidence schema and review gates; no device result
✓ Live-audio adapter contract
typed unavailable scaffold; no capture, playback or route
• Android foreground route RFC
draft evidence design; no Android adapter or device observation
• Live audio and encryption
planned
See roadmap →
diff --git a/site/ru/index.html b/site/ru/index.html index 61606b9..aaee836 100644 --- a/site/ru/index.html +++ b/site/ru/index.html @@ -15,7 +15,7 @@

Первый тест

Начните с воспроизводимого WAV round trip.

WAV исключает неизвестные свойства live-аудиотракта. Native app может экспортировать проверенный WAV и импортировать его для Rust-side validation; Acoustic-1 имеет controlled B-FSK carrier, а Acoustic-2 измеряет declared PCM transforms — ни один из них не является live-audio claim.

CLI / bootstrapcargo run -p adlp-cli -- encode-text hello.wav N1 "Привет" reliable
  cargo run -p adlp-cli -- decode hello.wav
Открыть краткое руководство →

Архитектура

Небольшое приложение поверх независимого Rust core.

FlutterUI, маршруты, выбор файла и диагностика
→
Rust bridgeОдна реализация codec для клиента
→
ADLP + DSPКонтейнер, WAV и будущие PHY profiles
Архитектура и стек →
-

Статус

Текущий срез — без обещаний сверх реализации.

✓ ADLP v1 + CRC-32C
готово
✓ Детерминированный PCM/WAV bootstrap
готово
✓ Flutter ↔ Rust bridge
native text/file-to-WAV verification
✓ Локальный WAV import / export
native dialogs + Rust validation
✓ Experimental file-object WAV
bounded local file selection, receipt и save; live route отсутствует
✓ Experimental Acoustic-1
B-FSK controlled PCM/WAV codec + golden vector
✓ Acoustic-2 measurement
declared PCM transforms + acquisition observables
✓ Device-acceptance protocol
evidence schema и review gates; device result отсутствует
✓ Live-audio adapter contract
typed unavailable scaffold; capture, playback и route отсутствуют
• Live audio и encryption
план
Посмотреть дорожную карту →
+

Статус

Текущий срез — без обещаний сверх реализации.

✓ ADLP v1 + CRC-32C
готово
✓ Детерминированный PCM/WAV bootstrap
готово
✓ Flutter ↔ Rust bridge
native text/file-to-WAV verification
✓ Локальный WAV import / export
native dialogs + Rust validation
✓ Experimental file-object WAV
bounded local file selection, receipt и save; live route отсутствует
✓ Experimental Acoustic-1
B-FSK controlled PCM/WAV codec + golden vector
✓ Acoustic-2 measurement
declared PCM transforms + acquisition observables
✓ Device-acceptance protocol
evidence schema и review gates; device result отсутствует
✓ Live-audio adapter contract
typed unavailable scaffold; capture, playback и route отсутствуют
• Android foreground route RFC
draft evidence design; Android adapter и device observation отсутствуют
• Live audio и encryption
план
Посмотреть дорожную карту →
diff --git a/spec/android-live-audio-adapter-v1.md b/spec/android-live-audio-adapter-v1.md new file mode 100644 index 0000000..743d283 --- /dev/null +++ b/spec/android-live-audio-adapter-v1.md @@ -0,0 +1,77 @@ +# Android foreground live-audio adapter v1 + +**Status:** Draft experimental platform-route design · **English (canonical)** · [Русский](android-live-audio-adapter-v1_RU.md) + +This RFC specializes the [constrained live-audio adapter v1](live-audio-adapter-v1.md) for a first Android implementation. It does not enable Android audio, establish a device result, or make Android, speaker-to-microphone, cable, Bluetooth, headset or radio support claim. + +## Scope + +The initial target is **Android API 26 or later**, while the application is visible in the foreground and after an explicit user action. The adapter may perform one selected operation at a time: + +| Operation | Native mechanism | Success boundary | +| --- | --- | --- | +| Playback | `AudioTrack` streaming writes of existing Rust-produced PCM | PCM was queued to an initialized track while audio focus was granted. It is not proof that another receiver decoded it. | +| Capture | `AudioRecord` reads of microphone PCM | PCM was read from an initialized record after permission grant. It is not proof that an ADLP object was acquired or decoded. | + +The requested application format is the existing `PcmStreamFormat.audioModemV1`: **48,000 Hz, one channel, signed PCM16 little-endian**. The adapter must read back its effective record/track format and reject the operation when it cannot preserve that invariant. It must not silently resample, remix, substitute float PCM or fall back to another sample rate. Android documents `AudioRecord` as a pull interface and `AudioTrack` as a PCM-push interface; their buffers and configuration must be created and checked explicitly. [1] [2] + +The adapter initially targets the OS-selected default capture/output path only. It does not call `setPreferredDevice()`, enumerate devices for a user selection, assert that a connected device is actually used, or support Bluetooth, cable, headset or radio-interface routing. A route-change notification terminates the active operation and records an observable diagnostic; it never implies successful rerouting. [1] [2] + +## Permission, focus and lifecycle + +`RECORD_AUDIO` is declared only when this RFC becomes an approved implementation change. Capture starts only after the user presses a capture action, the runtime permission is granted, and `AudioRecord` is initialized. The permission request must be contextual, cancellable and degrade to the existing WAV workflow when denied or revoked. [1] [3] + +Playback requests `AudioFocusRequest` immediately before `AudioTrack.play()`. A denied focus request rejects the attempt. A delayed request remains pending and must not write/play PCM until focus is granted. Any permanent or transient focus loss, route change, user stop, record/track failure or application lifecycle stop terminates the operation; v1 never auto-resumes. Android’s focus guidance requires applications to react to focus loss and abandon focus when playback ends. [4] + +```text +unavailable ──(API 26+ native adapter)──> idle +idle ──(user playback + granted focus + format verified)──> playing +idle ──(user capture + granted RECORD_AUDIO + format verified)──> capturing +idle ──(permission/focus/format failure)──> rejected ──(acknowledge)──> idle +playing|capturing ──(stop, focus loss, route change, lifecycle stop, native error)──> idle +any state ──(dispose)──> disposed +``` + +The current Flutter `LiveAudioAdapter` interface stays unchanged for this RFC. A later implementation may expose platform diagnostics through a separate event stream or status object, but must not expose unbounded raw microphone PCM as diagnostic data or make an unavailable adapter initialize Android audio APIs. + +## Native operation rules + +| Concern | Required behavior | Explicitly prohibited in v1 | +| --- | --- | --- | +| Capture | Build `AudioRecord` only after permission, use a buffer at least as large as the native minimum, verify initialized state, read bounded chunks, then stop and release. [1] | Background capture, retained ambient recordings, automatic retry after `ERROR_DEAD_OBJECT`, format fallback. | +| Playback | Build an initialized streaming `AudioTrack`, request/hold focus, write bounded chunks, stop, flush as appropriate, release and abandon focus. [2] [4] | Playback before focus, background/foreground-service playback, automatic resume, treating queued bytes as delivery. | +| Concurrent activity | Reject a second start while another operation is active. | Duplex operation, speaker-to-microphone loopback, echo cancellation or AGC. | +| Routing | Record the post-creation routed device category when observable; stop on a route change. | Declaring the requested/default/connected device to be a verified physical route. | +| Memory and privacy | Hold only operation buffers; discard captured PCM after hand-off or failure; retain textual diagnostics only. | Persisting raw PCM, callsigns or personal device identifiers automatically. | + +## Diagnostics + +Each operation produces a bounded, non-sensitive diagnostic record suitable for UI display and later acceptance reporting. It contains the adapter revision, Android API level, operation kind, requested/effective PCM format, initialization result, permission/focus outcome, route category if observable, stop reason, native error class and frame/byte counters. It excludes raw PCM, device serials, Bluetooth MAC addresses, user account names, callsigns and personal filesystem paths. + +An accepted operation means only that the platform-side lifecycle completed according to this RFC. A decoder result, physical-route result and supported-route label require separate gates under the [device-acceptance protocol](../docs/operations/device-acceptance.md). + +## Acceptance gates before enablement + +| Gate | Required evidence | Insufficient evidence | +| --- | --- | --- | +| Build gate | Reproducible Android target build, native unit/integration tests and no hidden plugin initialization. | A Dart-only fake or a manifest declaration. | +| Permission gate | Tests for granted, denied and revoked microphone permission; denied capture leaves no `AudioRecord`. | A single successful permission prompt. | +| Format gate | Tests reject unavailable/mismatched actual PCM format and show requested/effective values. | Requesting 48 kHz without inspecting the created object. | +| Focus/interruption gate | Tests cover focus denied, delayed, transient loss, permanent loss and user stop with no auto-resume. | Calling `play()` without an observed focus result. | +| Route gate | A route-specific device-acceptance schema extension, validator fixtures and reviewed `physical_route` measurements for the exact Android scope. | A connected accessory, an emulator run or a schema-valid generic template. | + +The first physical experiment may use only a declared `acoustic1` fixture and the existing report method once the route-specific evidence extension is merged. It must report all accepted, rejected and inconclusive runs. It may be labelled `observed` only after review, and it cannot create a broad Android compatibility claim. + +## Non-goals + +This RFC does not approve implementation yet. It excludes Android API levels below 26, Web, iOS, Windows, macOS and Linux adapters; `MediaRecorder`; direct Rust microphone access; Bluetooth/cable/radio routing; device picker UI; background service; notification controls; persistence/history; duplex; echo cancellation; AGC; resampling; timing recovery changes; physical performance metrics; authentication; encryption; file transfer changes; and a supported-route claim. + +## References + +[1] [Android Developers: `AudioRecord` API reference](https://developer.android.com/reference/android/media/AudioRecord) + +[2] [Android Developers: `AudioTrack` API reference](https://developer.android.com/reference/android/media/AudioTrack) + +[3] [Android Developers: Request runtime permissions](https://developer.android.com/training/permissions/requesting) + +[4] [Android Developers: Manage audio focus](https://developer.android.com/media/optimize/audio-focus) diff --git a/spec/android-live-audio-adapter-v1_RU.md b/spec/android-live-audio-adapter-v1_RU.md new file mode 100644 index 0000000..b9da78e --- /dev/null +++ b/spec/android-live-audio-adapter-v1_RU.md @@ -0,0 +1,79 @@ +# Android foreground live-audio adapter v1 + +[English (canonical)](android-live-audio-adapter-v1.md) · **Русский перевод** + +> **Translation of:** [spec/android-live-audio-adapter-v1.md](android-live-audio-adapter-v1.md). **Last synced:** 2026-08-21. + +**Статус:** Draft experimental platform-route design. Это не включает Android audio, не устанавливает device result и не заявляет Android, speaker-to-microphone, cable, Bluetooth, headset или radio support. + +## Scope + +Initial target — **Android API 26 или выше**, пока application visible в foreground и после explicit user action. Adapter выполняет только одну selected operation одновременно. + +| Операция | Native mechanism | Success boundary | +| --- | --- | --- | +| Playback | `AudioTrack` streaming writes существующего Rust-produced PCM | PCM queued в initialized track, пока audio focus granted. Это не доказывает, что другой receiver его decoded. | +| Capture | `AudioRecord` reads microphone PCM | PCM прочитан из initialized record после permission grant. Это не доказывает, что ADLP object acquired или decoded. | + +Requested application format — существующий `PcmStreamFormat.audioModemV1`: **48,000 Hz, one channel, signed PCM16 little-endian**. Adapter читает effective record/track format и отклоняет operation, если invariant не сохранён. Он не может silently resample, remix, substitute float PCM или fallback на другой sample rate. Android описывает `AudioRecord` как pull interface, а `AudioTrack` как PCM-push interface; их buffers и configuration создаются и проверяются явно. [1] [2] + +Первый adapter использует только OS-selected default capture/output path. Он не вызывает `setPreferredDevice()`, не enumerates devices для user selection, не утверждает, что connected device действительно используется, и не поддерживает Bluetooth, cable, headset или radio-interface routing. Route-change notification завершает active operation и записывает observable diagnostic; он не означает successful rerouting. [1] [2] + +## Permission, focus и lifecycle + +`RECORD_AUDIO` объявляется только когда этот RFC станет approved implementation change. Capture начинается лишь после нажатия user capture action, runtime permission grant и initialized `AudioRecord`. Permission request должен быть contextual и cancellable, а после denial или revocation application деградирует к существующему WAV workflow. [1] [3] + +Playback запрашивает `AudioFocusRequest` непосредственно перед `AudioTrack.play()`. Denied focus request отклоняет attempt. Delayed request остаётся pending и не может write/play PCM до focus grant. Permanent или transient focus loss, route change, user stop, record/track failure или application lifecycle stop завершает operation; v1 никогда auto-resume. Android focus guidance требует реакции на focus loss и abandon focus после playback end. [4] + +```text +unavailable ──(API 26+ native adapter)──> idle +idle ──(user playback + granted focus + format verified)──> playing +idle ──(user capture + granted RECORD_AUDIO + format verified)──> capturing +idle ──(permission/focus/format failure)──> rejected ──(acknowledge)──> idle +playing|capturing ──(stop, focus loss, route change, lifecycle stop, native error)──> idle +any state ──(dispose)──> disposed +``` + +Текущий Flutter `LiveAudioAdapter` interface для этого RFC не меняется. Later implementation может показать platform diagnostics через отдельный event stream или status object, но не должен exposing unbounded raw microphone PCM как diagnostic data или заставлять unavailable adapter инициализировать Android audio APIs. + +## Native operation rules + +| Concern | Required behavior | Явно запрещено в v1 | +| --- | --- | --- | +| Capture | Создать `AudioRecord` только после permission, использовать buffer не меньше native minimum, verify initialized state, читать bounded chunks, затем stop и release. [1] | Background capture, retained ambient recordings, automatic retry после `ERROR_DEAD_OBJECT`, format fallback. | +| Playback | Создать initialized streaming `AudioTrack`, request/hold focus, писать bounded chunks, stop, flush при необходимости, release и abandon focus. [2] [4] | Playback до focus, background/foreground-service playback, automatic resume, трактовка queued bytes как delivery. | +| Concurrent activity | Отклонять второй start, пока другая operation active. | Duplex, speaker-to-microphone loopback, echo cancellation или AGC. | +| Routing | Записывать post-creation routed device category, если observable; stop при route change. | Заявлять requested/default/connected device verified physical route. | +| Memory и privacy | Хранить только operation buffers; discard captured PCM после hand-off или failure; retain только textual diagnostics. | Автоматическое persistence raw PCM, callsigns или personal device identifiers. | + +## Diagnostics + +Каждая operation создаёт bounded, non-sensitive diagnostic record для UI и later acceptance reporting. Он содержит adapter revision, Android API level, operation kind, requested/effective PCM format, initialization result, permission/focus outcome, route category при observable, stop reason, native error class и frame/byte counters. Он исключает raw PCM, device serials, Bluetooth MAC addresses, user account names, callsigns и personal filesystem paths. + +Accepted operation означает только platform-side lifecycle completion согласно этому RFC. Decoder result, physical-route result и supported-route label требуют separate gates из [device-acceptance protocol](../docs/operations/device-acceptance_RU.md). + +## Acceptance gates before enablement + +| Gate | Required evidence | Insufficient evidence | +| --- | --- | --- | +| Build gate | Reproducible Android target build, native unit/integration tests и no hidden plugin initialization. | Dart-only fake или manifest declaration. | +| Permission gate | Tests для granted, denied и revoked microphone permission; denied capture не создаёт `AudioRecord`. | Один successful permission prompt. | +| Format gate | Tests отклоняют unavailable/mismatched actual PCM format и показывают requested/effective values. | Request 48 kHz без inspect created object. | +| Focus/interruption gate | Tests покрывают focus denied, delayed, transient loss, permanent loss и user stop без auto-resume. | Вызов `play()` без observed focus result. | +| Route gate | Route-specific device-acceptance schema extension, validator fixtures и reviewed `physical_route` measurements для exact Android scope. | Connected accessory, emulator run или schema-valid generic template. | + +Первый physical experiment использует только declared `acoustic1` fixture и existing report method после merge route-specific evidence extension. Он сообщает все accepted, rejected и inconclusive runs. Label `observed` возможен только после review и не создаёт broad Android compatibility claim. + +## Non-goals + +Этот RFC ещё не одобряет implementation. Он исключает Android API ниже 26, Web, iOS, Windows, macOS и Linux adapters; `MediaRecorder`; direct Rust microphone access; Bluetooth/cable/radio routing; device picker UI; background service; notification controls; persistence/history; duplex; echo cancellation; AGC; resampling; timing recovery changes; physical performance metrics; authentication; encryption; file-transfer changes и supported-route claim. + +## References + +[1] [Android Developers: `AudioRecord` API reference](https://developer.android.com/reference/android/media/AudioRecord) + +[2] [Android Developers: `AudioTrack` API reference](https://developer.android.com/reference/android/media/AudioTrack) + +[3] [Android Developers: Request runtime permissions](https://developer.android.com/training/permissions/requesting) + +[4] [Android Developers: Manage audio focus](https://developer.android.com/media/optimize/audio-focus) diff --git a/tools/device-acceptance/fixtures/android-speaker-microphone-template-v1.json b/tools/device-acceptance/fixtures/android-speaker-microphone-template-v1.json new file mode 100644 index 0000000..3d2d5e6 --- /dev/null +++ b/tools/device-acceptance/fixtures/android-speaker-microphone-template-v1.json @@ -0,0 +1,7 @@ +{ + "schema_version": "audio-modem-device-acceptance/v1", + "report_type": "template", + "report_id": "DARP-android-speaker-microphone-template", + "privacy_acknowledged": true, + "notes": "Unexecuted Android foreground speaker-to-microphone reporting template. Do not add device, test, evidence, outcome or adapter_observation fields until real hardware observations exist." +} diff --git a/tools/device-acceptance/test-validator.mjs b/tools/device-acceptance/test-validator.mjs index 8c2f0e6..8c47eae 100644 --- a/tools/device-acceptance/test-validator.mjs +++ b/tools/device-acceptance/test-validator.mjs @@ -18,4 +18,70 @@ assert.throws( /missing device/, ); +const androidSpeakerMicrophoneMeasurement = { + schema_version: 'audio-modem-device-acceptance/v1', + report_type: 'measurement', + report_id: 'DARP-android-validator-fixture', + privacy_acknowledged: true, + device: { + route: 'speaker_microphone', + source: { platform: 'android', device_class: 'built_in_speaker' }, + sink: { platform: 'android', device_class: 'built_in_microphone' }, + }, + test: { + app_revision: '1234567', + carrier: 'acoustic1', + profile: 'balanced', + fixture_path: 'crates/audio-modem-core/tests/fixtures/adlp-v1-acoustic1.wav', + fixture_sha256: 'a'.repeat(64), + command: 'validator fixture only; not a device observation', + }, + evidence: { + evidence_class: 'physical_route', + run_count: 1, + accepted_runs: 0, + rejected_runs: 1, + recording_availability: 'not_collected', + }, + outcome: { + classification: 'rejected', + summary: 'Validator fixture only; not a device observation.', + }, + adapter_observation: { + adapter_contract: 'android-live-audio-adapter/v1', + api_level: 26, + operation: 'playback_capture', + requested_format: { + sample_rate_hz: 48000, + channels: 1, + sample_format: 'pcm_s16le', + }, + effective_format: { + sample_rate_hz: 48000, + channels: 1, + sample_format: 'pcm_s16le', + }, + permission_state: 'granted', + focus_outcome: 'granted', + route_change_observed: false, + interruption_policy: 'stop_no_auto_resume', + raw_pcm_retention: 'discarded', + }, +}; +validateReport(androidSpeakerMicrophoneMeasurement); + +const missingAndroidObservation = structuredClone(androidSpeakerMicrophoneMeasurement); +delete missingAndroidObservation.adapter_observation; +assert.throws( + () => validateReport(missingAndroidObservation), + /missing adapter_observation/, +); + +const invalidAndroidFormat = structuredClone(androidSpeakerMicrophoneMeasurement); +invalidAndroidFormat.adapter_observation.effective_format.sample_rate_hz = 44100; +assert.throws( + () => validateReport(invalidAndroidFormat), + /effective_format.sample_rate_hz must be 48000/, +); + console.log('device-acceptance validator tests passed'); diff --git a/tools/device-acceptance/validate-report.mjs b/tools/device-acceptance/validate-report.mjs index 43e4d9f..06c8780 100644 --- a/tools/device-acceptance/validate-report.mjs +++ b/tools/device-acceptance/validate-report.mjs @@ -16,6 +16,7 @@ const RECORDING_AVAILABILITY = new Set([ 'public_unrestricted', ]); const OUTCOME_CLASSES = new Set(['observed', 'inconclusive', 'rejected']); +const ANDROID_ADAPTER_CONTRACT = 'android-live-audio-adapter/v1'; function fail(message) { throw new Error(message); @@ -71,6 +72,79 @@ function validateEndpoint(value, field) { } } +function isAndroidSpeakerMicrophone(report) { + return ( + report.device?.route === 'speaker_microphone' && + report.device?.source?.platform === 'android' && + report.device?.sink?.platform === 'android' + ); +} + +function validateAudioModemV1Format(value, field) { + const format = object(value, field); + onlyKeys(format, field, new Set(['sample_rate_hz', 'channels', 'sample_format'])); + if (required(format, 'sample_rate_hz') !== 48000) { + fail(`${field}.sample_rate_hz must be 48000`); + } + if (required(format, 'channels') !== 1) { + fail(`${field}.channels must be 1`); + } + if (required(format, 'sample_format') !== 'pcm_s16le') { + fail(`${field}.sample_format must be pcm_s16le`); + } +} + +function validateAndroidAdapterObservation(value) { + const observation = object(value, 'adapter_observation'); + onlyKeys( + observation, + 'adapter_observation', + new Set([ + 'adapter_contract', + 'api_level', + 'operation', + 'requested_format', + 'effective_format', + 'permission_state', + 'focus_outcome', + 'route_change_observed', + 'interruption_policy', + 'raw_pcm_retention', + ]), + ); + if (required(observation, 'adapter_contract') !== ANDROID_ADAPTER_CONTRACT) { + fail('adapter_observation.adapter_contract is unsupported'); + } + integer(required(observation, 'api_level'), 'adapter_observation.api_level', 26); + if (required(observation, 'operation') !== 'playback_capture') { + fail('adapter_observation.operation must be playback_capture'); + } + validateAudioModemV1Format( + required(observation, 'requested_format'), + 'adapter_observation.requested_format', + ); + validateAudioModemV1Format( + required(observation, 'effective_format'), + 'adapter_observation.effective_format', + ); + if (required(observation, 'permission_state') !== 'granted') { + fail('adapter_observation.permission_state must be granted'); + } + const focusOutcome = required(observation, 'focus_outcome'); + if (focusOutcome !== 'granted' && focusOutcome !== 'delayed_then_granted') { + fail('adapter_observation.focus_outcome is unsupported'); + } + if (typeof required(observation, 'route_change_observed') !== 'boolean') { + fail('adapter_observation.route_change_observed must be a boolean'); + } + if (required(observation, 'interruption_policy') !== 'stop_no_auto_resume') { + fail('adapter_observation.interruption_policy must be stop_no_auto_resume'); + } + if (required(observation, 'raw_pcm_retention') !== 'discarded') { + fail('adapter_observation.raw_pcm_retention must be discarded'); + } +} + function validateMeasurement(report) { for (const field of ['device', 'test', 'evidence', 'outcome']) { required(report, field); @@ -120,11 +194,17 @@ function validateMeasurement(report) { fail('outcome.classification is unsupported'); } string(required(outcome, 'summary'), 'outcome.summary', { min: 1, max: 1000 }); + + if (isAndroidSpeakerMicrophone(report)) { + validateAndroidAdapterObservation(required(report, 'adapter_observation')); + } else if ('adapter_observation' in report) { + fail('adapter_observation is only allowed for Android speaker_microphone measurements'); + } } export function validateReport(report) { const value = object(report, 'report'); - onlyKeys(value, 'report', new Set(['schema_version', 'report_type', 'report_id', 'privacy_acknowledged', 'notes', 'device', 'test', 'evidence', 'outcome'])); + onlyKeys(value, 'report', new Set(['schema_version', 'report_type', 'report_id', 'privacy_acknowledged', 'notes', 'device', 'test', 'evidence', 'outcome', 'adapter_observation'])); if (required(value, 'schema_version') !== SCHEMA_VERSION) { fail('schema_version is unsupported'); } @@ -140,7 +220,7 @@ export function validateReport(report) { string(value.notes, 'notes', { min: 1, max: 1000 }); } if (reportType === 'template') { - for (const field of ['device', 'test', 'evidence', 'outcome']) { + for (const field of ['device', 'test', 'evidence', 'outcome', 'adapter_observation']) { if (field in value) { fail(`template report must not contain ${field}`); }