Closes the mobile half of the PoC plan §2 audio exit criterion
left open by poc/audio-capture-playback-spike. The desktop and
mobile halves together fully retire the audio PoC.
Stack:
Kotlin (MainActivity) → JNI → Rust cdylib
→ cpal 0.16 → Oboe (AAudio / OpenSL ES) → Android audio HAL
Layout:
rust crate (src/lib.rs) — JNI_OnLoad, initContext,
playSine440, record1sToFile;
panic-catching at JNI boundary;
android_logger → logcat
android/ (Gradle 8.7, — minSdk 24, compileSdk 34, AGP 8.5.2.
AGP 8.5.2, Kotlin 1.9.24) cargoBuildRust task wraps cargo-ndk
-P 26 -t <abi> for all four ABIs;
wired into preBuild so AGP picks up
the produced .so files.
Verified on 2026-05-13 on a physical Motorola Moto G Stylus 5G
(2023), Android 14 SDK 34 arm64-v8a:
- Playback: 500 ms 440 Hz mono sine, 22,050 frames emitted at
44.1 kHz through cpal/Oboe/AAudio/device speaker.
- Capture: 1 s from default input, 42,624 frames written to
/data/data/app.chanora.poc.audio/files/chanora_poc_capture.wav.
File pulled via 'adb exec-out run-as ... cat' and confirmed
by file(1) as 'RIFF (little-endian) data, WAVE audio,
Microsoft PCM, 16 bit, mono 44100 Hz'. Header bytes
cross-checked against the reported frame count.
Notes:
- cpal links libaaudio (introduced API 26), so cargo-ndk targets
API 26 via -P 26 while the Android module's minSdk stays at 24
(DEC-004). API 24/25 devices would fall back to OpenSL ES at
runtime; not exercised here.
- JNI panic safety: every JNI entry point wraps its body in
std::panic::catch_unwind and a tracing panic hook routes
panic messages to logcat under tag 'ChanoraAudioPoC'. Without
this, cpal panicking inside an extern "system" function would
abort the process.
- The emulator AVD chanora-poc-api34 and its system image were
installed during Phase 0 but emulator verification was skipped
once the physical-device run succeeded. Real-device evidence
is stronger.
Surfaced finding: DEC-011.1 mobile half promoted from Deferred to
Accepted for Android in the same docs commit; iOS remains
explicitly Deferred (requires macOS + Xcode hardware).
Authority: PoC plan §2, DEC-011, DEC-011.1.
Not product code; not promoted into chanora_audio.
Chanora — Proofs of Concept
This directory holds non-product technical spikes. They exist to retire risk before the Flutter/Rust product scaffold is created.
Authority: docs/architecture/proof-of-concept-plan.md.
Rule (PoC plan §4): A PoC is not product code unless explicitly promoted. Nothing here should be imported by the future
apps/chanora_flutterorcrates/chanora_*trees.
Status
| Spike | Required by PoC plan | Status |
|---|---|---|
tsclientlib-connect-spike |
yes | PASS — see tsclientlib-connect-spike/VERIFICATION.md |
flutter_rust_bridge_hello |
yes (flutter-rust-bridge-hello in plan) |
PASS — see flutter_rust_bridge_hello/VERIFICATION.md |
secure-storage-spike |
yes | PASS (Linux only) — see secure-storage-spike/VERIFICATION.md |
audio-capture-playback-spike |
yes | PARTIAL PASS — desktop verified, mobile not — see audio-capture-playback-spike/VERIFICATION.md |
sqlite-storage-spike |
yes | PASS — see sqlite-storage-spike/VERIFICATION.md |
diagnostics-redaction-spike |
yes | PASS — see diagnostics-redaction-spike/VERIFICATION.md |
Naming note
flutter_rust_bridge_hello is listed in the PoC plan as
flutter-rust-bridge-hello. The directory on disk uses underscores
because the Dart pubspec.yaml package-name grammar forbids hyphens.
This is a naming convention, not a scope change.
What "verified" means here
Each verified spike contains:
- a
README.mddescribing scope, exit criterion, and reproduction; - a
VERIFICATION.mdrecording the exact toolchain versions, command, observed output, and an honest list of things the spike does not validate.
Spikes are intentionally narrow. They do not approximate Chanora architecture layering, they do not implement the documented DTO catalogue, and they do not stand in for the product Rust crates or Flutter modules.