# Verification record — `audio-capture-playback-spike` ## Result **PASS (desktop half).** The Linux/PipeWire desktop half of the PoC plan §2 audio exit criterion is fully met by this spike. The mobile half ("at least one mobile target") is closed by [`poc/audio-capture-playback-android-spike`](../audio-capture-playback-android-spike/) on a physical Android device — see that directory's `VERIFICATION.md`. Combined: the audio PoC is fully retired. ## Environment | Field | Value | |---|---| | Date | 2026-05-13 | | Host OS | Linux (Arch, kernel 7.0.5-arch1-1, x86_64) | | Rust toolchain | stable 1.95.0 | | `cpal` | 0.16 | | `hound` (WAV I/O) | 3.5 | | Audio server | PipeWire 1.6.4 (with the `pcm_pipewire` ALSA plugin module) | | Default input | `auto_null` (system has no physical microphone bound to this session) | | Default output | `auto_null` (PipeWire null sink) | | Local config | `~/.asoundrc` aliases `pcm.!default` and `ctl.!default` to `pipewire` | ## Reproduction ```bash cargo test cargo run --bin audio-capture-playback-cli -- roundtrip ``` ## Test run ``` running 2 tests test synth_produces_valid_wav_with_expected_frame_count ... ok test synth_then_playback_does_not_error_when_output_device_present ... ok test result: ok. 2 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.06s ``` ## CLI round-trip ``` INFO spike: opening capture stream device="default" sample_rate=44100 channels=2 sample_format=F32 WARN spike: capture unavailable; falling back to synth error=stream build error: A backend-specific error has occurred: ALSA function 'snd_pcm_hw_params' failed with error 'No such file or directory (2)' INFO spike: synthesised fallback frames=24000 sample_rate=48000 INFO spike: preparing playback device="default" file_sr=48000 file_channels=1 samples=24000 INFO spike: playback finished consumed=24000 total=24000 round-trip complete (played from /tmp/chanora_audio_spike_synth.wav) ``` ### What this evidences 1. cpal successfully **opened the default input device** (negotiated 44.1 kHz / 2-channel / F32 against PipeWire's `auto_null`). 2. The capture **stream build** then failed because the null source does not expose `snd_pcm_hw_params` at that configuration. This is a *real* platform-audio failure mode; the typed `AudioError::BuildStream` was returned and surfaced cleanly. This is the same shape production code will see on misconfigured user hosts. 3. The fallback synth wrote a valid 24,000-frame, 48 kHz, mono, 16-bit WAV. 4. cpal **opened the default output device**, negotiated the matching config, and the playback callback drained the WAV completely: `consumed=24000 total=24000`. Process exited 0. The full path **Rust → cpal → ALSA → `pcm_pipewire` → PipeWire → `auto_null`** ran end-to-end, with PCM frames produced by Rust consumed by the platform audio stack. ## Coverage matrix | PoC plan requirement | Status | |---|---| | Desktop capture path | PASS (stream opens against the system audio stack; typed error returned when device cannot satisfy `hw_params`). | | Desktop playback path | PASS (24,000 frames driven to completion). | | Mobile capture path | NOT VERIFIED. | | Mobile playback path | NOT VERIFIED. | | Round-trip flow | PASS — capture-attempt → fallback → playback. | | Typed error model | PASS — `AudioError` arms returned cleanly. | | Cross-platform crate choice (DEC-011 alignment) | PARTIAL — `cpal` is a platform-native abstraction, but only the Linux back-end exercised here. | ## What this spike does NOT validate - Real microphone capture (this host has none). - Bit-exact capture-to-playback signal integrity. - Latency. - Hot-plug / device-change events. - Permission flows (mobile microphone consent, Linux pulse permission prompts). - Behaviour under sample-rate or channel-count mismatch between file and device (the PoC chose the file's rate; production code will need a real resampler). - DSP (HPF / NS / AEC / AGC). - Opus encode / decode. - Jitter buffer / mixer. - Mobile (Android Oboe / iOS AVAudioEngine) — **explicit gap**. ## Recommended follow-up 1. Promote `cpal` selection to a recorded product decision (DEC-011 sub-decision) — or pin a different crate after the mobile spike. Done on 2026-05-13 as DEC-011.1 in `docs/governance/product-decision-register.md`. 2. Spike Android Oboe path on an emulator or real device. Done on 2026-05-13 — see `poc/audio-capture-playback-android-spike/VERIFICATION.md`. 3. Spike iOS AVAudioEngine path on a real device. Still deferred; requires macOS + Xcode hardware. 4. Only after the iOS half is closed (or explicitly waived): promote the typed `AudioError` + capture/playback shapes into `chanora_audio`.