Files
chanora/docs/designs/ios-audio-session-test-plan.md
T
Edison Jwa 72ded4e011 docs: add test tool designs and iOS test plan (TODO-020,043,044,046,047)
Design documents for event replay, audio processing test, audio
loopback test, and protocol probe tools. iOS audio session test
plan with 28 scenarios and 3-phase automation approach.
2026-06-11 21:04:45 +09:00

9.1 KiB

iOS Audio Session Lifecycle — Integration Test Plan

Overview

Chanora uses Apple's VoiceProcessingIO (VPIO) AudioUnit on iOS/macOS for voice capture and playback. The audio session is configured in Swift (AppDelegate) with AVAudioSession.Category.playAndRecord and AVAudioSession.Mode.default. This document defines the integration tests needed to verify correct behavior across session transitions, interruptions, and route changes.

Architecture Summary

Layer Responsibility
AppDelegate.swift Sets AVAudioSession category/mode, handles route-change and interruption notifications
IosVoiceUnit (Rust) Opens VPIO AudioUnit, pins 48 kHz Int16 mono, installs render + input callbacks
AudioEngine::ios_restart_voice_unit Restarts the VPIO unit after a route change
AudioEngine::ios_pause_voice_unit / ios_resume_voice_unit Suspends audio during interruptions
route_policy.rs Maps AudioRoute to recommended AudioProcessingConfig (AEC/NS/AGC ownership)

Test Scenarios

1. Session Activation and Deactivation

ID Scenario Steps Expected Behavior
S-01 Cold start session activation Launch app → connect to server → join voice channel VPIO unit starts, mic input flows, audio plays through default route
S-02 Session deactivation on disconnect While in voice → disconnect from server VPIO unit stops, AudioEngine::stop() called, session category restored
S-03 Session mode verification After activation, query AVAudioSession.mode Must be .default (not .voiceChat) to avoid ducking

2. Audio Interruption Handling

ID Scenario Steps Expected Behavior
I-01 Phone call interruption While in voice → receive incoming call AVAudioSession.interruptionNotification fires with .began; VPIO paused via ios_pause_voice_unit
I-02 Phone call ends After I-01 → call ends Interruption notification fires with .ended; VPIO resumed via ios_resume_voice_unit if session was active
I-03 Siri activation While in voice → invoke Siri Interruption .began → pause; Siri dismisses → .ended → resume
I-04 Alarm / timer While in voice → alarm fires Audio ducks (not interrupted); voice continues at reduced volume
I-05 Third-party audio app While in voice → open Spotify and play music Other audio ducks; Chanora voice remains active
I-06 Interruption during route change While switching routes → phone call arrives Both events handled; no crash, VPIO restarts cleanly after both resolve

3. Route Change Handling

ID Scenario Steps Expected Behavior
R-01 Plug in wired headset While on speaker → connect Lightning/USB-C headphones Route changes to .wiredHeadset; ios_restart_voice_unit called; AEC disabled (no acoustic echo path); route_policy.rs returns WiredHeadset config
R-02 Unplug wired headset While on wired headset → disconnect Route changes to .speaker; VPIO restarts; AEC re-enabled via platform VPIO
R-03 Connect Bluetooth HFP While on speaker → connect BT headset in HFP mode Route changes to .bluetoothHfp; VPIO restarts; AEC off (headset firmware handles it)
R-04 Disconnect Bluetooth HFP While on BT HFP → turn off headset Route falls back to speaker; VPIO restarts with platform AEC
R-05 Switch to Bluetooth A2DP While on speaker → connect A2DP-only device Route changes to .bluetoothA2dp; transmit blocked (A2DP is output-only); playback continues
R-06 Toggle speaker/earpiece Use in-app audio output picker overrideOutputAudioPort called; VPIO restarts; audio actually moves (not just metadata)
R-07 AirPods connect/disconnect While on speaker → AirPods connect → AirPods case closed Route transitions handled; VPIO restarts on each change
R-08 Rapid route changes Connect/disconnect headset 5 times in 10 seconds No crash, no audio leak, VPIO restarts cleanly each time
R-09 Route change during mute While muted → route changes VPIO restarts; mute state preserved; no audio leak

4. Audio Ducking Configuration

ID Scenario Steps Expected Behavior
D-01 Ducking disabled on startup App launches and joins voice kAUVoiceIOProperty_OtherAudioDuckingConfiguration set with mEnableAdvancedDucking=0, mDuckingLevel=Min
D-02 Music playback while in voice Play music via Music app → join voice channel Music volume is NOT heavily attenuated; voice and music coexist
D-03 Game audio while in voice Play a game with audio → join voice Game audio is NOT heavily attenuated

5. VPIO Stream Format Verification

ID Scenario Steps Expected Behavior
F-01 Output bus format After VPIO start, inspect bus 0 stream format 48 kHz, Int16, mono, signed integer, packed
F-02 Input bus format After VPIO start, inspect bus 1 stream format 48 kHz, Int16, mono, signed integer, packed
F-03 Callback frame count Log num_frames in render callback iOS: 480 frames (10 ms); macOS: 512 frames (10.67 ms)
F-04 Audio quality roundtrip Speak into mic → loopback to speaker No distortion, no resampling artifacts, correct latency

6. Route Policy Correctness

ID Scenario Steps Expected Behavior
P-01 Speaker route policy Route = Speaker ios_route_policy returns: AEC=Platform, NS=Platform, AGC=Platform, backend=PlatformVoiceProcessing
P-02 Wired headset policy Route = WiredHeadset AEC=Off, NS=Conservative, AGC=Conservative, backend=Noop
P-03 BT HFP policy Route = BluetoothHfp AEC=Off, NS=Conservative, AGC=Conservative, backend=PlatformVoiceProcessing
P-04 A2DP policy Route = BluetoothA2dp All processing off, VAD disabled, transmit blocked
P-05 INV-009 invariant Any route Sonora AEC never enabled simultaneously with platform VPIO
P-06 User VAD preserved on route change Set VAD=WebRTC → change route New config keeps VAD=WebRTC and hangover timing

Device Requirements

Required Devices

Device OS Reason
iPhone (Lightning or USB-C) iOS 16+ Primary target; VPIO, route changes, interruptions
iPhone with Face ID iOS 17+ OtherAudioDuckingConfiguration property availability
AirPods (any generation) Bluetooth A2DP/HFP route testing
Bluetooth HFP headset Non-Apple BT headset route testing
Lightning/USB-C wired headset Wired route testing
iPad (optional) iPadOS 16+ Verify identical VPIO behavior

Simulator Limitations

  • VPIO render callback cadence differs from real hardware
  • Route changes are not testable on simulator
  • Interruption notifications are unreliable on simulator
  • Recommendation: All integration tests must run on physical devices

Automation Approach

Phase 1: Manual Test Matrix

Execute scenarios S-01 through P-06 on physical devices using this checklist. Record pass/fail and any audio artifacts observed.

Phase 2: XCUITest + Rust Harness

┌─────────────┐     ┌──────────────┐     ┌───────────────┐
│  XCUITest    │────▶│  FRB bridge  │────▶│  AudioEngine  │
│  (Swift)     │     │  test helper │     │  (Rust)       │
└─────────────┘     └──────────────┘     └───────────────┘
  1. FRB test helper: Add a #[flutter_rust_bridge::frb] test function that starts AudioEngine, runs for N seconds, and returns stats (frames_sent, frames_received, xruns, output_underruns).

  2. XCUITest: Launches the app, connects to a test server, triggers the FRB helper, then uses XCUIDevice APIs to simulate:

    • Route changes via XCUIDevice.shared().press(.volumeUp) + BT pairing
    • Interruptions via XCUISiriService (Siri) or call simulation
  3. Assertions: Verify stats counters are within expected ranges (no xruns, no output underruns, frames_sent > 0).

Phase 3: Continuous Monitoring

Add a telemetry event for each VPIO restart, pause, resume, and interruption. Track:

  • Restart count per session (should be ≤ number of route changes)
  • Pause-to-resume latency (should be < 500 ms)
  • Xrun count per session (should be 0 under normal conditions)

References