53 lines
1.7 KiB
Python
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)
|