Files
jarvis/jarvis/audio/vad.py
T

53 lines
1.7 KiB
Python

import numpy as np
import logging
log = logging.getLogger(__name__)
class VAD:
def is_speech(self, audio: np.ndarray, sr: int = 16000) -> bool:
raise NotImplementedError
class SileroVAD(VAD):
def __init__(self, threshold=0.5):
self.threshold = threshold
self.model = None
self._load()
def _load(self):
try:
from silero_vad import load_silero_vad
self.model = load_silero_vad()
log.info("Silero VAD loaded")
except Exception as e:
log.warning(f"Silero VAD load failed: {e}, fallback to energy")
self.model = None
def is_speech(self, audio, sr=16000):
if self.model is None:
return float(np.abs(audio).mean()) > 0.01
try:
import torch
from silero_vad import get_speech_timestamps
if len(audio) < 512:
return False
tensor = torch.from_numpy(audio.astype(np.float32))
stamps = get_speech_timestamps(tensor, self.model, sampling_rate=sr, threshold=self.threshold, min_speech_duration_ms=250, min_silence_duration_ms=100)
return len(stamps) > 0
except Exception as e:
log.debug(f"VAD error: {e}")
return float(np.abs(audio).mean()) > 0.01
class EnergyVAD(VAD):
def __init__(self, threshold=0.01):
self.threshold = threshold
def is_speech(self, audio, sr=16000):
return float(np.mean(np.abs(audio))) > self.threshold
def create_vad(cfg):
engine = cfg.get('vad', {}).get('engine', 'silero')
thresh = cfg.get('vad', {}).get('threshold', 0.5)
if engine == 'silero':
return SileroVAD(threshold=thresh)
else:
return EnergyVAD(threshold=0.02)