From 08d7ace25d6262ac28eff0eaffc065d7ba1f4b86 Mon Sep 17 00:00:00 2001 From: Edison Jwa Date: Wed, 10 Jun 2026 09:18:32 +0900 Subject: [PATCH] fix(audio,flutter): restore VAD on iOS and fix Android build MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add iOS to voiceActivityTransmitAvailable — iOS has CoreML Silero VAD pipeline (AppleCoreMlVadWorker) but was excluded by DEC-030 gating that predated the CoreML integration - Inline deleted VadWorkerPolicy in android_voice_unit.rs — PR #37 removed the enum from vad/mod.rs but missed updating Android --- .../lib/widgets/voice_settings_controls.dart | 9 ++- .../chanora_audio/src/android_voice_unit.rs | 55 +++++++------------ 2 files changed, 24 insertions(+), 40 deletions(-) diff --git a/apps/chanora_flutter/lib/widgets/voice_settings_controls.dart b/apps/chanora_flutter/lib/widgets/voice_settings_controls.dart index 3eee65c..f52ffea 100644 --- a/apps/chanora_flutter/lib/widgets/voice_settings_controls.dart +++ b/apps/chanora_flutter/lib/widgets/voice_settings_controls.dart @@ -65,13 +65,12 @@ List> transmitModeSegmentsFor({ /// True when this host advertises VAD transmit per DEC-030. /// /// The desktop Silero ONNX + WebRTC fallback ships on Windows and -/// Linux; the Android Oboe + WebRTC path covers Android. iOS and -/// macOS still rely on the Apple VoiceProcessingIO unit and have no -/// Chanora-owned VAD pipeline, so the bridge cannot honour the -/// voice-activity transmit mode there. +/// Linux; the Android Oboe + WebRTC path covers Android; iOS uses the +/// Apple CoreML Silero VAD pipeline via `vad::apple_coreml`. macOS is +/// still gated until its VAD pipeline is confirmed. bool get voiceActivityTransmitAvailable { if (kIsWeb) return false; - return Platform.isWindows || Platform.isLinux || Platform.isAndroid; + return Platform.isWindows || Platform.isLinux || Platform.isAndroid || Platform.isIOS; } /// Android hardware/WebRTC selector segments. diff --git a/crates/chanora_audio/src/android_voice_unit.rs b/crates/chanora_audio/src/android_voice_unit.rs index 69290d0..75ed66f 100644 --- a/crates/chanora_audio/src/android_voice_unit.rs +++ b/crates/chanora_audio/src/android_voice_unit.rs @@ -375,38 +375,20 @@ impl AndroidCaptureState { speech: true, } } else if vad_backend == crate::VadBackend::SileroOnnx { - match crate::vad::callback_vad_worker_policy( - voice_activity_mode, - vad_backend, - self.silero_vad_worker.is_some(), - ) { - crate::vad::VadWorkerPolicy::UseWorker => { - let worker = self - .silero_vad_worker - .as_ref() - .expect("policy checked worker"); - let enqueued = worker.try_send(capture_seq, &frame); - if !worker.is_stale(capture_seq) { - let p = worker.latest_probability(); - crate::vad::VadOutput { - probability: p, - speech: p >= 0.5, - } - } else if enqueued { - crate::vad::VadOutput { - probability: 0.0, - speech: false, - } - } else { - used_fallback_vad = true; - self.mark_vad_fallback_active(vad_backend); - crate::vad::VoiceActivityDetector::process_10ms( - &mut self.vad_detector, - &frame, - ) + if let Some(worker) = self.silero_vad_worker.as_ref() { + let enqueued = worker.try_send(capture_seq, &frame); + if !worker.is_stale(capture_seq) { + let p = worker.latest_probability(); + crate::vad::VadOutput { + probability: p, + speech: p >= 0.5, } - } - crate::vad::VadWorkerPolicy::UseFallback => { + } else if enqueued { + crate::vad::VadOutput { + probability: 0.0, + speech: false, + } + } else { used_fallback_vad = true; self.mark_vad_fallback_active(vad_backend); crate::vad::VoiceActivityDetector::process_10ms( @@ -414,10 +396,13 @@ impl AndroidCaptureState { &frame, ) } - crate::vad::VadWorkerPolicy::NotModelBacked => crate::vad::VadOutput { - probability: 1.0, - speech: true, - }, + } else { + used_fallback_vad = true; + self.mark_vad_fallback_active(vad_backend); + crate::vad::VoiceActivityDetector::process_10ms( + &mut self.vad_detector, + &frame, + ) } } else { crate::vad::VoiceActivityDetector::process_10ms(&mut self.vad_detector, &frame)