# Verification record — `audio-capture-playback-android-spike` ## Result PASS. The mobile half of the PoC plan §2 audio exit criterion ("at least one mobile target") is met on a physical Android device. This combined with the desktop half (closed by `poc/audio-capture-playback-spike` on Linux + PipeWire on 2026-05-13) fully retires the audio PoC. ## Environment | Field | Value | |---|---| | Date | 2026-05-13 | | Build host OS | Linux (Arch, kernel 7.0.5-arch1-1, x86_64) | | Rust toolchain | stable 1.95.0 (59807616e 2026-04-14) | | Android SDK | platform 34, build-tools 34.0.0, platform-tools | | Android NDK | r26.3.11579264 | | `cargo-ndk` | 4.1.2 | | AGP | 8.5.2 | | Gradle | 8.7 | | Kotlin | 1.9.24 | | `cpal` | 0.16 | | `hound` | 3.5 | | `jni` | 0.21 | | `ndk-context` | 0.1.1 | | `android_logger` | 0.14 | | Test device | Motorola Moto G Stylus 5G (2023), Android 14 (SDK 34), arm64-v8a (transport_id ZD222DQHFY) | The Android x86_64 emulator AVD `chanora-poc-api34` was created and the system image installed, but emulator verification was **skipped intentionally** once the physical-device run succeeded; the physical device is stronger evidence than emulator audio HAL. ## Reproduction ```bash cd poc/audio-capture-playback-android-spike/android ./gradlew :app:assembleDebug adb install -r app/build/outputs/apk/debug/app-debug.apk adb shell pm grant app.chanora.poc.audio android.permission.RECORD_AUDIO adb shell am start -n app.chanora.poc.audio/.MainActivity adb shell input tap 540 520 # Play adb shell input tap 540 640 # Record ``` ## Observed result ### Playback (PLAY 500 MS SINE) logcat: ``` ChanoraPoCActivity: playing… ChanoraAudioPoC: audio_capture_playback_android_spike: playback device='default' sample_rate=44100 channels=2 fmt=F32 ChanoraAudioPoC: audio_capture_playback_android_spike: playSine440 ok n=22050 ChanoraPoCActivity: PLAY OK frames=22050 ``` UI status text after tap: `"PLAY OK frames=22050"`. 22,050 frames at 44.1 kHz mono equals exactly 500 ms — the requested duration. The full Rust → cpal → Oboe → AAudio → speaker path completed without error. ### Capture (RECORD 1 S TO FILE) logcat: ``` ChanoraPoCActivity: recording 1 s… ChanoraAudioPoC: audio_capture_playback_android_spike: capture device='default' sample_rate=44100 channels=2 fmt=F32 ChanoraAudioPoC: audio_capture_playback_android_spike: record1sToFile ok n=42624 ChanoraPoCActivity: RECORD OK frames=42624 bytes=85292 path=/data/user/0/app.chanora.poc.audio/files/chanora_poc_capture.wav ``` UI status text after tap: `"RECORD OK frames=42624 bytes=85292 path=/data/user/0/app.chanora.poc.audio/files/chanora_poc_capture.wav"`. 42,624 frames at 44.1 kHz is approximately 966 ms — slightly under the requested 1 s because `std::thread::sleep` and the cpal callback boundary do not align perfectly. The full Rust → cpal → Oboe → AAudio → microphone path completed without error. ### File integrity The WAV file was pulled with: ```bash adb exec-out run-as app.chanora.poc.audio cat files/chanora_poc_capture.wav \ > /tmp/chanora_capture.wav ``` `file(1)` output: ``` /tmp/chanora_capture.wav: RIFF (little-endian) data, WAVE audio, Microsoft PCM, 16 bit, mono 44100 Hz ``` Header bytes confirm: ``` 00000000: 5249 4646 244d 0100 5741 5645 666d 7420 RIFF$M..WAVEfmt 00000010: 1000 0000 0100 0100 44ac 0000 8858 0100 ........D....X.. 00000020: 0200 1000 6461 7461 004d 0100 0000 0000 ....data.M...... ``` - `RIFF` magic, size 85,284 + 8. - `WAVE` format, fmt chunk: PCM (0x0001), mono (0x0001), 44100 Hz (0x0000AC44), 88200 bytes/s, block-align 2, 16 bits/sample. - `data` chunk size 0x00014D00 = 85,248 bytes of PCM ≈ 42,624 i16 samples — matches the reported frame count. ## Coverage matrix | PoC plan exit criterion | Status | Evidence | |---|---|---| | Capture/playback works on at least one **desktop** target | PASS | `poc/audio-capture-playback-spike/VERIFICATION.md` | | Capture/playback works on at least one **mobile** target | **PASS** | This document. | ## What this spike does NOT validate - iOS (deferred per DEC-011.1; requires macOS + Xcode hardware). - Android emulator audio path (skipped — physical device is stronger evidence). - API 24–25 devices (cpal's OpenSL ES fallback path is not exercised here; only AAudio via API 26+). - Bit-exact loopback signal integrity from capture to playback. - Audio latency under load. - Streaming reconfiguration on device change or audio focus loss. - Background-service / foreground-service audio (required for a real voice client; out of scope here). - DSP, Opus encode/decode, jitter buffer, mixer — owned by `chanora_audio`. - Privacy-sensitive UX (microphone consent flows beyond a single runtime grant). ## Decisions closed by this spike - **DEC-011.1** sub-decision is promoted from `Accepted (desktop) / Deferred (mobile)` to `Accepted (desktop: cpal; Android: cpal-on-Oboe) / Deferred (iOS)`. See `docs/governance/product-decision-register.md` v0.9.4. - The audio row in `docs/architecture/proof-of-concept-plan.md` §2 is promoted from PARTIAL PASS to PASS. - RISK-PoC-001 in `docs/governance/poc-results-summary.md` is closed for Android; iOS portion remains open.