feat(voice): add iOS VAD runtime support

This commit is contained in:
Edison Jwa
2026-05-21 20:51:45 +09:00
parent 171baf6e41
commit 6af4ecab0f
73 changed files with 11529 additions and 1249 deletions
+22 -6
View File
@@ -11,8 +11,7 @@
//! on PTT mode)
//!
//! Hard-mute is a final clamp; leaving the channel forces the gate
//! to `false`. `VoiceActivity` is treated identically to
//! `Continuous` per DEC-030 until a VAD implementation lands.
//! to `false`. `VoiceActivity` is driven by Rust-owned VAD state.
//!
//! All four inputs are stored as atomics so any thread can update
//! them without taking a lock. After each update we call
@@ -92,6 +91,7 @@ pub struct TransmitModeSelector {
in_channel: AtomicBool,
hard_mute: AtomicBool,
ptt_held: AtomicBool,
voice_activity_open: AtomicBool,
/// SDD-106 §5/§6 / SRS-209: latest resolved microphone
/// permission state. Stored as a `u8` so writes from the
/// JNI thread (Android permission requester → bridge) and
@@ -133,6 +133,7 @@ impl TransmitModeSelector {
in_channel: AtomicBool::new(false),
hard_mute: AtomicBool::new(false),
ptt_held: AtomicBool::new(false),
voice_activity_open: AtomicBool::new(false),
// SDD-106 §5: default to Granted on construction so
// non-Android hosts (which never publish a permission
// event) are not silently clamped. The Android bridge
@@ -226,6 +227,17 @@ impl TransmitModeSelector {
self.ptt_held.load(Ordering::Relaxed)
}
/// Rust-owned VAD gate input for VoiceActivity mode.
pub fn set_voice_activity_open(&self, v: bool) {
self.voice_activity_open.store(v, Ordering::Relaxed);
self.recompute();
}
/// Current Rust-owned VAD gate state.
pub fn voice_activity_open(&self) -> bool {
self.voice_activity_open.load(Ordering::Relaxed)
}
/// Subscribe to `ptt_held` transitions. Used by the
/// missed-key-up watchdog (SAD-079) so it fires on the actual
/// PTT-key-down lifetime, not on the resolved `transmit_active`
@@ -265,8 +277,8 @@ impl TransmitModeSelector {
}
match self.mode() {
TransmitMode::Ptt => self.ptt_held.load(Ordering::Relaxed),
// DEC-030: VoiceActivity behaves as Continuous in v1.
TransmitMode::Continuous | TransmitMode::VoiceActivity => true,
TransmitMode::Continuous => true,
TransmitMode::VoiceActivity => self.voice_activity_open.load(Ordering::Relaxed),
}
}
@@ -323,13 +335,17 @@ mod tests {
assert!(g.load(), "continuous independent of key state");
}
/// SWE4-UV-037: voice-activity mode matches continuous in v1.
/// SWE4-UV-037: voice-activity mode follows VAD state.
#[test]
fn voice_activity_matches_continuous_v1() {
fn voice_activity_requires_vad_open() {
let (g, s) = fresh();
s.set_mode(TransmitMode::VoiceActivity);
s.set_in_channel(true);
assert!(!g.load());
s.set_voice_activity_open(true);
assert!(g.load());
s.set_voice_activity_open(false);
assert!(!g.load());
}
/// SWE4-UV-037 / SWE4-UV-041: hard-mute clamps the transmit