feat: stabilize voice activity and audio routing
This commit is contained in:
@@ -92,9 +92,6 @@ impl AudioBackend {
|
||||
pub enum VadBackend {
|
||||
/// Silero ONNX VAD. P1 schema default when model/runtime exist.
|
||||
SileroOnnx,
|
||||
/// TEN VAD backend. Native TEN runtime is optional; unavailable
|
||||
/// builds fall back to the realtime-safe WebRTC detector.
|
||||
TenVad,
|
||||
/// WebRTC-style fallback VAD.
|
||||
WebrtcVad,
|
||||
/// Debug-only energy VAD.
|
||||
@@ -108,7 +105,6 @@ impl VadBackend {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
Self::SileroOnnx => "silero_vad_onnx",
|
||||
Self::TenVad => "ten_vad",
|
||||
Self::WebrtcVad => "webrtc_vad",
|
||||
Self::EnergyDebug => "energy_debug",
|
||||
Self::Disabled => "disabled",
|
||||
@@ -168,7 +164,7 @@ impl Default for AudioProcessingConfig {
|
||||
route: AudioRoute::Speaker,
|
||||
ios_mode: IosVoiceProcessingMode::PlatformVoiceProcessing,
|
||||
processing_backend: AudioBackend::PlatformVoiceProcessing,
|
||||
vad_backend: VadBackend::TenVad,
|
||||
vad_backend: VadBackend::SileroOnnx,
|
||||
aec: EffectOwner::Platform,
|
||||
// iOS VPIO owns NS/AGC on the default shipping path. Software
|
||||
// effects are opt-in through the experimental raw route only.
|
||||
@@ -246,7 +242,7 @@ mod tests {
|
||||
assert_eq!(config.aec, EffectOwner::Platform);
|
||||
assert_eq!(config.ns, EffectOwner::Platform);
|
||||
assert_eq!(config.agc, EffectOwner::Platform);
|
||||
assert_eq!(config.vad_backend, VadBackend::TenVad);
|
||||
assert_eq!(config.vad_backend, VadBackend::SileroOnnx);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -259,11 +255,6 @@ mod tests {
|
||||
assert!(config.validate_for_ios().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ten_vad_has_stable_debug_string() {
|
||||
assert_eq!(VadBackend::TenVad.as_str(), "ten_vad");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn raw_processing_allows_full_webrtc_apm_chain() {
|
||||
let config = AudioProcessingConfig {
|
||||
@@ -342,6 +333,16 @@ pub struct AudioProcessingStats {
|
||||
pub callback_xruns: u64,
|
||||
/// Clipped sample count.
|
||||
pub clipped_samples: u64,
|
||||
/// Number of effectively silent processed capture frames.
|
||||
pub zero_frames: u64,
|
||||
/// Number of processed capture frames.
|
||||
pub capture_frames: u64,
|
||||
/// Number of input callbacks carrying 10 ms of audio.
|
||||
pub callbacks_10ms: u64,
|
||||
/// Number of input callbacks carrying 20 ms of audio.
|
||||
pub callbacks_20ms: u64,
|
||||
/// Number of input callbacks carrying any other size.
|
||||
pub callbacks_other: u64,
|
||||
/// Sonora enabled.
|
||||
pub sonora_enabled: bool,
|
||||
/// Platform voice processing enabled.
|
||||
@@ -361,6 +362,11 @@ pub struct SharedAudioProcessingStats {
|
||||
output_underruns: AtomicU64,
|
||||
callback_xruns: AtomicU64,
|
||||
clipped_samples: AtomicU64,
|
||||
zero_frames: AtomicU64,
|
||||
capture_frames: AtomicU64,
|
||||
callbacks_10ms: AtomicU64,
|
||||
callbacks_20ms: AtomicU64,
|
||||
callbacks_other: AtomicU64,
|
||||
actual_sample_rate_hz: AtomicU32,
|
||||
actual_io_buffer_frames: AtomicU32,
|
||||
}
|
||||
@@ -379,6 +385,11 @@ impl Default for SharedAudioProcessingStats {
|
||||
output_underruns: AtomicU64::new(0),
|
||||
callback_xruns: AtomicU64::new(0),
|
||||
clipped_samples: AtomicU64::new(0),
|
||||
zero_frames: AtomicU64::new(0),
|
||||
capture_frames: AtomicU64::new(0),
|
||||
callbacks_10ms: AtomicU64::new(0),
|
||||
callbacks_20ms: AtomicU64::new(0),
|
||||
callbacks_other: AtomicU64::new(0),
|
||||
actual_sample_rate_hz: AtomicU32::new(crate::frame::SAMPLE_RATE_HZ),
|
||||
actual_io_buffer_frames: AtomicU32::new(crate::frame::FRAME_20MS_SAMPLES as u32),
|
||||
}
|
||||
@@ -412,6 +423,12 @@ impl SharedAudioProcessingStats {
|
||||
.store(io_buffer_frames, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Record the actual device sample rate.
|
||||
pub fn set_actual_sample_rate_hz(&self, sample_rate_hz: u32) {
|
||||
self.actual_sample_rate_hz
|
||||
.store(sample_rate_hz, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Increment output underrun count.
|
||||
pub fn increment_output_underrun(&self) {
|
||||
self.output_underruns.fetch_add(1, Ordering::Relaxed);
|
||||
@@ -427,6 +444,32 @@ impl SharedAudioProcessingStats {
|
||||
self.clipped_samples.fetch_add(count, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
/// Record one processed capture frame and whether it was effectively silent.
|
||||
pub fn record_capture_frame(&self, zero_frame: bool) {
|
||||
self.capture_frames.fetch_add(1, Ordering::Relaxed);
|
||||
if zero_frame {
|
||||
self.zero_frames.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// Bucket callback delivery sizes to diagnose timing jitter and packetization.
|
||||
pub fn record_callback_frames(&self, frames: u64) {
|
||||
let sample_rate_hz = self.actual_sample_rate_hz.load(Ordering::Relaxed).max(1);
|
||||
let frames_10ms = (sample_rate_hz / 100) as u64;
|
||||
let frames_20ms = (sample_rate_hz / 50) as u64;
|
||||
match frames {
|
||||
value if value == frames_10ms => {
|
||||
self.callbacks_10ms.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
value if value == frames_20ms => {
|
||||
self.callbacks_20ms.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
_ => {
|
||||
self.callbacks_other.fetch_add(1, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Store whether the selected VAD backend is currently using a fallback.
|
||||
pub fn set_vad_fallback_active(&self, active: bool) {
|
||||
self.vad_fallback_active.store(active, Ordering::Relaxed);
|
||||
@@ -452,6 +495,11 @@ impl SharedAudioProcessingStats {
|
||||
output_underruns: self.output_underruns.load(Ordering::Relaxed),
|
||||
callback_xruns: self.callback_xruns.load(Ordering::Relaxed),
|
||||
clipped_samples: self.clipped_samples.load(Ordering::Relaxed),
|
||||
zero_frames: self.zero_frames.load(Ordering::Relaxed),
|
||||
capture_frames: self.capture_frames.load(Ordering::Relaxed),
|
||||
callbacks_10ms: self.callbacks_10ms.load(Ordering::Relaxed),
|
||||
callbacks_20ms: self.callbacks_20ms.load(Ordering::Relaxed),
|
||||
callbacks_other: self.callbacks_other.load(Ordering::Relaxed),
|
||||
sonora_enabled: config.processing_backend == AudioBackend::Sonora,
|
||||
platform_voice_processing_enabled: config.processing_backend
|
||||
== AudioBackend::PlatformVoiceProcessing,
|
||||
|
||||
Reference in New Issue
Block a user