Detección de actividad vocal
Hay dos modelos de VAD disponibles: Pyannote segmentation para procesamiento offline por lotes con alta precisión, y Silero VAD v5 para detección en streaming de baja latencia. Ambos se ejecutan completamente en el dispositivo.
Pyannote (offline)
Pyannote segmentation-3.0 proporciona VAD de alta precisión utilizando una arquitectura PyanNet. Procesa el audio en ventanas deslizantes de 10 segundos con un paso de 1 segundo, luego agrega las predicciones solapadas y aplica suavizado por histéresis.
Arquitectura
| Etapa | Detalles |
|---|---|
| SincNet | 40 filtros pasa-banda aprendidos (80 en total: 40 cos + 40 sin) |
| BiLSTM | 4 capas, hidden=128, bidireccional (salida de 256 dim) |
| Linear | 2 capas lineales con LeakyReLU (negative_slope=0.01) |
| Salida | Softmax de 7 clases con post-procesamiento por histéresis |
Tamaño del modelo: ~1,49M parámetros, ~5,7 MB en disco.
Umbrales por defecto
- Onset:
0.767— probabilidad por encima de la cual se detecta voz - Offset:
0.377— probabilidad por debajo de la cual termina la voz
Uso desde la CLI
# VAD offline
.build/release/audio vad recording.wav
# Salida JSON
.build/release/audio vad recording.wav --json
# Umbrales personalizados
.build/release/audio vad recording.wav --onset 0.6 --offset 0.3
Silero VAD v5 (streaming)
Silero VAD v5 es un modelo ligero de streaming que procesa chunks de 512 muestras (32 ms a 16 kHz). Se ejecuta a 23x el tiempo real en modo release, lo que lo hace adecuado para aplicaciones de audio en vivo.
Arquitectura
| Etapa | Detalles |
|---|---|
| STFT | Conv1d (de 1 a 258 canales), reflection pad a la derecha de 64 |
| Encoder | 4x Conv1d + ReLU |
| LSTM | Hidden size 128, estado mantenido entre chunks |
| Decoder | Conv1d (de 128 a 1) sobre el estado oculto del LSTM, salida sigmoide |
Tamaño del modelo: ~309K parámetros, ~1,2 MB en disco.
Máquina de estados en streaming
El procesador VAD en streaming usa una máquina de 4 estados para producir segmentos de voz limpios:
- silence — no se detecta voz
- pendingSpeech — se cruzó el umbral de onset, esperando la duración mínima de voz
- speech — segmento de voz confirmado en curso
- pendingSilence — se cruzó el umbral de offset, esperando la duración mínima de silencio
Umbrales por defecto
- Onset:
0.5 - Offset:
0.35 - Duración mínima de voz:
0.25s - Duración mínima de silencio:
0.1s
Uso desde la CLI
# VAD en streaming
.build/release/audio vad-stream recording.wav
# Umbrales personalizados
.build/release/audio vad-stream recording.wav --onset 0.6 --offset 0.3
# Duraciones mínimas
.build/release/audio vad-stream recording.wav --min-speech 0.5 --min-silence 0.2
# Elegir motor
.build/release/audio vad-stream recording.wav --engine coreml
Opciones
| Opción | Aplica a | Descripción |
|---|---|---|
--onset | Ambos | Umbral de probabilidad de inicio de voz |
--offset | Ambos | Umbral de probabilidad de fin de voz |
--min-speech | Streaming | Duración mínima del segmento de voz (segundos) |
--min-silence | Streaming | Duración mínima de silencio para terminar un segmento (segundos) |
--engine | Streaming | Motor de inferencia: mlx o coreml |
--json | Ambos | Formato de salida JSON |
Para aplicaciones en tiempo real, usa audio vad-stream con Silero VAD. El modelo Pyannote requiere el archivo de audio completo y es más adecuado para procesamiento offline por lotes donde la precisión es la prioridad.
Descargas de modelos
| Modelo | Backend | Tamaño | HuggingFace |
|---|---|---|---|
| Silero-VAD-v5 | MLX | ~1,2 MB | aufklarer/Silero-VAD-v5-MLX |
| Silero-VAD-v5 | CoreML | ~1,2 MB | aufklarer/Silero-VAD-v5-CoreML |
| Pyannote-Segmentation-3.0 | MLX | ~5,7 MB | aufklarer/Pyannote-Segmentation-MLX |
API Swift
import SpeechVAD
// VAD offline (Pyannote)
let pyannote = try await PyannoteVAD.loadFromHub()
let segments = try await pyannote.detectSpeech(audioFile: "recording.wav")
for segment in segments {
print("\(segment.start)s - \(segment.end)s")
}
// VAD en streaming (Silero)
let silero = try await SileroVAD.loadFromHub()
let processor = StreamingVADProcessor(model: silero, config: .sileroDefault)
for chunk in audioChunks {
if let segment = try processor.process(chunk: chunk) {
print("Speech: \(segment.start)s - \(segment.end)s")
}
}
También disponible en Android y Linux vía ONNX Runtime.