feat: stabilize voice activity and audio routing

This commit is contained in:
Edison Jwa
2026-05-25 01:19:09 +09:00
parent eb9014cd81
commit 5515ff6643
34 changed files with 3054 additions and 1751 deletions
-74
View File
@@ -7,7 +7,6 @@
pub mod resampler;
pub mod silero_onnx;
pub mod ten_onnx;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{OnceLock, RwLock};
@@ -17,7 +16,6 @@ use crate::AudioError;
use resampler::{Downsampler48to16, INPUT_FRAME_10MS};
pub use silero_onnx::SileroOnnxVad;
pub use ten_onnx::{TenOnnxVad, TenOnnxVadWorker};
/// Voice activity detector output for one 10 ms frame.
#[derive(Debug, Clone, Copy)]
@@ -108,17 +106,11 @@ pub fn process_i16_10ms(detector: &mut dyn VoiceActivityDetector, samples: &[i16
static SILERO_MODEL_PATH_OVERRIDE: OnceLock<RwLock<Option<String>>> = OnceLock::new();
static SILERO_MODEL_EPOCH: AtomicU64 = AtomicU64::new(0);
static TEN_MODEL_PATH_OVERRIDE: OnceLock<RwLock<Option<String>>> = OnceLock::new();
static TEN_MODEL_EPOCH: AtomicU64 = AtomicU64::new(0);
fn silero_model_path_override() -> &'static RwLock<Option<String>> {
SILERO_MODEL_PATH_OVERRIDE.get_or_init(|| RwLock::new(None))
}
fn ten_model_path_override() -> &'static RwLock<Option<String>> {
TEN_MODEL_PATH_OVERRIDE.get_or_init(|| RwLock::new(None))
}
/// Configure the preferred Silero ONNX model path.
///
/// The path is validated eagerly. A successful call increments the
@@ -149,32 +141,6 @@ pub fn silero_model_epoch() -> u64 {
SILERO_MODEL_EPOCH.load(Ordering::Relaxed)
}
/// Configure the preferred TEN VAD ONNX model path.
pub fn set_ten_model_path(path: &str) -> Result<(), AudioError> {
let path = path.trim();
if path.is_empty() {
return Err(AudioError::InvalidAudioProcessingConfig(
"ten vad model path must not be empty".to_string(),
));
}
if !std::path::Path::new(path).is_file() {
return Err(AudioError::InvalidAudioProcessingConfig(format!(
"ten vad model path does not exist or is not a file: {path}"
)));
}
let mut guard = ten_model_path_override()
.write()
.map_err(|_| AudioError::Backend("ten vad model path lock poisoned".to_string()))?;
*guard = Some(path.to_string());
TEN_MODEL_EPOCH.fetch_add(1, Ordering::Relaxed);
Ok(())
}
/// Monotonic counter incremented whenever the configured TEN model path changes.
pub fn ten_model_epoch() -> u64 {
TEN_MODEL_EPOCH.load(Ordering::Relaxed)
}
/// Return the expected path of the Silero VAD v6 ONNX model.
/// The model is shipped as a Flutter asset and copied to the app's
/// data directory by the Dart-side asset loader.
@@ -231,46 +197,6 @@ pub fn silero_model_bundle_path() -> String {
}
}
/// Return the expected path of the TEN VAD ONNX model copied by Flutter.
pub fn ten_model_bundle_path() -> String {
if let Ok(guard) = ten_model_path_override().read() {
if let Some(path) = guard.as_ref() {
return path.clone();
}
}
#[cfg(any(target_os = "ios", target_os = "macos"))]
{
if let Ok(home) = std::env::var("HOME") {
let docs = format!("{home}/Documents/ten_vad.onnx");
if std::path::Path::new(&docs).exists() {
return docs;
}
let bundle = format!("{home}/../Library/ten_vad.onnx");
if std::path::Path::new(&bundle).exists() {
return bundle;
}
}
"ten_vad.onnx".to_string()
}
#[cfg(target_os = "android")]
{
"ten_vad.onnx".to_string()
}
#[cfg(not(any(target_os = "ios", target_os = "macos", target_os = "android")))]
{
if let Ok(cwd) = std::env::current_dir() {
let local = cwd.join("ten_vad.onnx");
if local.exists() {
return local.to_string_lossy().to_string();
}
}
"ten_vad.onnx".to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;