first
This commit is contained in:
+110
@@ -0,0 +1,110 @@
|
||||
//! Voice Activity Detection (Silero VAD v5 via ONNX Runtime).
|
||||
//!
|
||||
//! The detector runs on the 16 kHz mono stream produced by the session layer
|
||||
//! (the same stream fed to the STT engine) and emits three kinds of events:
|
||||
//!
|
||||
//! - [`VadEvent::SpeechOnset`] — fast onset, used to interrupt the agent
|
||||
//! (barge-in) as soon as the user starts talking.
|
||||
//! - [`VadEvent::SpeechStart`] — confirmed utterance start.
|
||||
//! - [`VadEvent::SpeechEnd`] — utterance finished, carrying the captured audio
|
||||
//! (with a pre-roll pad) that is handed to the STT engine.
|
||||
//!
|
||||
//! Every behavioural knob is configured from the environment (see
|
||||
//! [`VadSettings`]), so the feature can be tuned or disabled without a rebuild.
|
||||
|
||||
pub mod silero;
|
||||
|
||||
pub use silero::{SileroVad, VadDetector, VadEvent};
|
||||
|
||||
use crate::config::{env_bool, env_f32, env_u32};
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Runtime configuration for the VAD subsystem, read from `.env`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct VadSettings {
|
||||
/// Master switch (`VAD_ENABLED`). When false the legacy RMS segmentation
|
||||
/// inside the STT worker is used instead.
|
||||
pub enabled: bool,
|
||||
/// Path to `silero_vad.onnx` (`VAD_MODEL_PATH`).
|
||||
pub model_path: PathBuf,
|
||||
/// Speech probability above which a window counts as voice
|
||||
/// (`VAD_THRESHOLD`, default 0.5).
|
||||
pub threshold: f32,
|
||||
/// Probability below which a window counts as silence
|
||||
/// (`VAD_NEG_THRESHOLD`). Must be lower than `threshold`; the gap is the
|
||||
/// hysteresis that prevents rapid onset/offset flapping.
|
||||
pub neg_threshold: f32,
|
||||
/// Speech required to confirm an utterance (`VAD_MIN_SPEECH_MS`).
|
||||
pub min_speech_ms: u32,
|
||||
/// Silence required to close an utterance — the EOU window
|
||||
/// (`VAD_MIN_SILENCE_MS`).
|
||||
pub min_silence_ms: u32,
|
||||
/// Audio kept before the detected onset so the first phoneme is not
|
||||
/// clipped (`VAD_SPEECH_PAD_MS`).
|
||||
pub speech_pad_ms: u32,
|
||||
/// Speech required to fire the barge-in onset (`VAD_BARGE_IN_MIN_SPEECH_MS`).
|
||||
pub barge_in_min_speech_ms: u32,
|
||||
/// Feed the STT engine with VAD-segmented utterances instead of letting it
|
||||
/// run its own RMS segmentation (`VAD_SEGMENT_STT`).
|
||||
pub segment_stt: bool,
|
||||
/// Interrupt the agent when the user starts speaking over it
|
||||
/// (`VAD_BARGE_IN`).
|
||||
pub barge_in: bool,
|
||||
/// Log the speech probability of every analysis window (`VAD_DEBUG`).
|
||||
pub debug: bool,
|
||||
}
|
||||
|
||||
impl Default for VadSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
enabled: true,
|
||||
model_path: PathBuf::from("models/silero_vad.onnx"),
|
||||
threshold: 0.5,
|
||||
neg_threshold: 0.35,
|
||||
min_speech_ms: 250,
|
||||
min_silence_ms: 300,
|
||||
speech_pad_ms: 30,
|
||||
barge_in_min_speech_ms: 150,
|
||||
segment_stt: true,
|
||||
barge_in: true,
|
||||
debug: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl VadSettings {
|
||||
/// Build the settings from environment variables, falling back to defaults.
|
||||
pub fn from_env() -> Self {
|
||||
let mut s = Self::default();
|
||||
if let Ok(p) = std::env::var("VAD_MODEL_PATH") {
|
||||
if !p.is_empty() {
|
||||
s.model_path = PathBuf::from(p);
|
||||
}
|
||||
}
|
||||
s.enabled = env_bool("VAD_ENABLED", s.enabled);
|
||||
s.segment_stt = env_bool("VAD_SEGMENT_STT", s.segment_stt);
|
||||
s.barge_in = env_bool("VAD_BARGE_IN", s.barge_in);
|
||||
s.debug = env_bool("VAD_DEBUG", s.debug);
|
||||
s.threshold = env_f32("VAD_THRESHOLD", s.threshold);
|
||||
s.neg_threshold = env_f32("VAD_NEG_THRESHOLD", s.neg_threshold);
|
||||
s.min_speech_ms = env_u32("VAD_MIN_SPEECH_MS", s.min_speech_ms);
|
||||
s.min_silence_ms = env_u32("VAD_MIN_SILENCE_MS", s.min_silence_ms);
|
||||
s.speech_pad_ms = env_u32("VAD_SPEECH_PAD_MS", s.speech_pad_ms);
|
||||
s.barge_in_min_speech_ms = env_u32("VAD_BARGE_IN_MIN_SPEECH_MS", s.barge_in_min_speech_ms);
|
||||
|
||||
// Hysteresis must stay below the speech threshold, and the fast
|
||||
// barge-in onset can never be slower than the utterance onset.
|
||||
s.neg_threshold = s.neg_threshold.min(s.threshold);
|
||||
s.barge_in_min_speech_ms = s.barge_in_min_speech_ms.min(s.min_speech_ms);
|
||||
s
|
||||
}
|
||||
}
|
||||
|
||||
/// Samples retained ahead of a detected onset (pre-roll + triggering speech).
|
||||
pub(crate) fn preroll_samples(settings: &VadSettings) -> usize {
|
||||
let ms = settings
|
||||
.speech_pad_ms
|
||||
.saturating_add(settings.min_speech_ms)
|
||||
.saturating_add(silero::WINDOW_MS);
|
||||
(crate::config::STT_SAMPLE_RATE as u64 * ms as u64 / 1000) as usize
|
||||
}
|
||||
Reference in New Issue
Block a user