feat: Android Oboe voice backend — WebRTC APM, VAD, HW/SW toggle, BBCode welcome, link trust, foreground task
Audio engine (Rust): - Android Oboe: WebRTC APM (AEC/NS/AGC/HPF) + TEN/Silero ONNX VAD - Hardware effects (JNI) with software fallback per-effect - Render reference buffer for AEC between output/capture callbacks - Voice activity gate: suppress transmission when speaker muted (all platforms) - Audio focus (SDD-109) + Bluetooth SCO (SDD-110) via JNI - ONNX Runtime 1.26 via ort 2.0.0-rc.12 (down from rc.10, ndarray 0.17) - VAD worker channel capacity 8→32, initial seq u64::MAX (warm-up fix) - TEN VAD default backend (was Silero) - Platform→WebrtcApm resolution after hardware binding - oboe-rs edisonjwa fork with get_raw_session_id() Android Kotlin: - AndroidAudioFocusController + AndroidBluetoothScoController - AndroidAudioLifecycleController (route changes to Flutter) - ProGuard rules for new controllers Flutter UI: - VoiceSettings: Android HW/SW toggle (Platform auto / WebRTC APM) - VoiceStatusChip: mute warning border + Speaker muted label - BBCode welcome message parser (BbCodeText, case-insensitive) - Welcome message foldable (expanded by default) - Link trust dialog (domain wildcards, SharedPreferences) - HapticFeedback on voice sheet opener - Server name in AppBar, version v0.1.0 - Default channel (id=1) visible, serverquery clients hidden - flutter_foreground_task integration Config: - ort load-dynamic on all non-iOS (Android/Linux/Windows) - ONNX Runtime AAR 1.26.0 - ndarray moved to common deps (was Apple-only)
This commit is contained in:
@@ -1725,6 +1725,21 @@ pub async fn set_vad_model_path(path: String) -> Result<(), BridgeError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Configure the TEN VAD ONNX model path.
|
||||
pub async fn set_ten_vad_model_path(path: String) -> Result<(), BridgeError> {
|
||||
if path.trim().is_empty() {
|
||||
return Err(BridgeError::InvalidCommand(
|
||||
"ten vad model path must not be empty".to_string(),
|
||||
));
|
||||
}
|
||||
runtime()
|
||||
.spawn(async move { chanora_audio::vad::set_ten_model_path(&path).map_err(|e| e) })
|
||||
.await
|
||||
.map_err(|e| task_join_error("set_ten_vad_model_path", e))?
|
||||
.map_err(|e| BridgeError::Unmapped(format!("set_ten_vad_model_path: {e}")))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Enable or disable audio debug WAV dumping.
|
||||
pub async fn enable_audio_debug_wav_dump(enabled: bool) -> Result<(), BridgeError> {
|
||||
runtime()
|
||||
@@ -1746,20 +1761,20 @@ pub async fn set_ios_voice_processing_mode(
|
||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => {
|
||||
BridgeAudioBackend::PlatformVoiceProcessing
|
||||
}
|
||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeAudioBackend::Sonora,
|
||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeAudioBackend::WebrtcApm,
|
||||
},
|
||||
vad_backend: BridgeVadBackend::SileroOnnx,
|
||||
aec: match mode {
|
||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => BridgeEffectOwner::Platform,
|
||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::Sonora,
|
||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::WebrtcApm,
|
||||
},
|
||||
ns: match mode {
|
||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => BridgeEffectOwner::Platform,
|
||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::Sonora,
|
||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::WebrtcApm,
|
||||
},
|
||||
agc: match mode {
|
||||
BridgeIosVoiceProcessingMode::PlatformVoiceProcessing => BridgeEffectOwner::Platform,
|
||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::Sonora,
|
||||
BridgeIosVoiceProcessingMode::SonoraExperimental => BridgeEffectOwner::WebrtcApm,
|
||||
},
|
||||
hpf_enabled: true,
|
||||
limiter_enabled: true,
|
||||
|
||||
Reference in New Issue
Block a user