Diarización de hablantes
Identifica quién habló cuándo en una grabación con varios hablantes. Hay dos motores de diarización disponibles: un pipeline Pyannote de dos etapas (segmentación + encadenamiento de hablantes por actividad, luego embedding posterior) y un modelo Sortformer de extremo a extremo (CoreML, Neural Engine).
Motores
Selecciona el motor con --engine pyannote (por defecto) o --engine sortformer.
Pyannote (por defecto)
Pipeline de dos etapas: la segmentación Pyannote procesa ventanas solapadas con encadenamiento de hablantes por actividad (correlación de Pearson en las zonas de solapamiento) para asignar etiquetas globales de hablante. La extracción posterior de embeddings WeSpeaker permite identificar un hablante objetivo mediante audio de enrolamiento.
Sortformer (CoreML)
Modelo neural de diarización de extremo a extremo de NVIDIA. Predice directamente la actividad por frame de hasta 4 hablantes sin etapas separadas de embedding o clustering. Se ejecuta en Neural Engine vía CoreML con buffers de estado en streaming (FIFO + caché de hablantes).
Sortformer no produce embeddings de hablante. Los flags --target-speaker y --embedding-engine solo están disponibles con el motor Pyannote.
Pipeline Pyannote
El pipeline por defecto se ejecuta en dos etapas:
Etapa 1: segmentación + encadenamiento de hablantes
Pyannote segmentation-3.0 procesa ventanas deslizantes de 10 segundos con un 50% de solapamiento. Un decodificador powerset convierte la salida de 7 clases en probabilidades por hablante (hasta 3 hablantes locales por ventana). Las ventanas adyacentes comparten un solapamiento de 5 segundos — la identidad del hablante se propaga entre ventanas calculando la correlación de Pearson entre las trayectorias de probabilidad en la zona de solapamiento, con emparejamiento exclusivo goloso para obtener IDs de hablante globales consistentes.
Etapa 2: embedding posterior
Tras la diarización, WeSpeaker ResNet34-LM extrae un embedding centroide de 256 dimensiones por hablante. Estos embeddings permiten la extracción de un hablante objetivo (--target-speaker) pero no condicionan la asignación de hablantes en sí.
Migrar desde pyannote.audio
Si vienes de la biblioteca Python pyannote.audio — reemplazando una subclase de Pipeline que define pipeline.segmentation = ..., o migrando desde un servidor que aloja pyannote/speaker-diarization-3.1 — Soniqo envuelve el mismo modelo Pyannote-Segmentation-3.0 y lo ejecuta completamente en el dispositivo en Apple Silicon. Sin runtime de Python, sin CUDA, sin token de Hugging Face en la inferencia.
| pyannote.audio (Python) | Soniqo (Swift) |
|---|---|
Pipeline.from_pretrained("pyannote/speaker-diarization-3.1") |
DiarizationPipeline.fromPretrained() |
pipeline(audio_file) |
pipeline.diarize(audio: samples, sampleRate: 16000) |
pipeline.segmentation = ... (subclase personalizada) |
Fijo: Pyannote-Segmentation-3.0 (MLX o CoreML, autoseleccionado) |
diarization.itertracks(yield_label=True) |
for seg in result.segments { ... } |
diarization.write_rttm(file) |
CLI: --rttm |
pyannote.metrics.diarization.DiarizationErrorRate |
CLI: --score-against reference.rttm |
Los pesos de Pyannote-Segmentation-3.0 están convertidos desde el checkpoint upstream de HuggingFace, por lo que los logits de segmentación son numéricamente equivalentes dentro de la tolerancia de precisión flotante. El encadenamiento posterior a la segmentación (correlación de Pearson en ventanas superpuestas + emparejamiento exclusivo voraz) y las fases de embedding post-hoc con WeSpeaker se reimplementan en Swift, pero producen una salida RTTM comparable a la pipeline Python de referencia.
No hay un equivalente streaming de OnlineSpeakerDiarization para el motor Pyannote. Para diarización en tiempo real usa --engine sortformer, que ejecuta el modelo Sortformer con buffers de estado FIFO + caché de hablantes.
Uso desde la CLI
# Diarización básica (pyannote, por defecto)
.build/release/speech diarize meeting.wav
# Sortformer de extremo a extremo (CoreML)
.build/release/speech diarize meeting.wav --engine sortformer
# Formato de salida RTTM (para evaluación)
.build/release/speech diarize meeting.wav --rttm
# Salida JSON
.build/release/speech diarize meeting.wav --json
Extracción de hablante objetivo
Proporciona audio de enrolamiento de un hablante conocido para extraer solo sus segmentos de una grabación. El pipeline calcula el embedding del audio de enrolamiento y encuentra el cluster con la mayor similitud coseno.
# Extraer los segmentos de un hablante concreto
.build/release/speech diarize meeting.wav --target-speaker enrollment.wav
Puntuación DER
Evalúa la calidad de la diarización comparándola con un archivo RTTM de referencia. El pipeline calcula la Diarization Error Rate (DER), que mide la proporción de tiempo atribuido incorrectamente.
# Puntuar contra un RTTM de referencia
.build/release/speech diarize meeting.wav --score-against reference.rttm
Salida RTTM
El flag --rttm produce salida en Rich Transcription Time Marked, un formato estándar usado para la evaluación de diarización. Cada línea sigue el formato:
SPEAKER filename 1 start_time duration <NA> <NA> speaker_id <NA> <NA>
Opciones
| Opción | Descripción |
|---|---|
--target-speaker | Audio de enrolamiento para la extracción de un hablante objetivo (solo pyannote) |
--embedding-engine | Motor de embedding de hablante: mlx o coreml (solo pyannote) |
--vad-filter | Pre-filtrado con Silero VAD (solo pyannote) |
--rttm | Salida en formato RTTM |
--json | Formato de salida JSON |
--score-against | Archivo RTTM de referencia para evaluación DER |
La diarización funciona mejor con grabaciones que tienen turnos de hablante claros. Las voces altamente solapadas pueden reducir la precisión. El número de hablantes se determina automáticamente.
Descargas de modelos
Los modelos se descargan automáticamente en el primer uso:
| Componente | Modelo | Tamaño | HuggingFace |
|---|---|---|---|
| Segmentación | Pyannote-Segmentation-3.0 | ~5,7 MB | aufklarer/Pyannote-Segmentation-MLX |
| Embedding de hablante | WeSpeaker-ResNet34-LM (MLX) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-MLX |
| Embedding de hablante | WeSpeaker-ResNet34-LM (CoreML) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-CoreML |
| Sortformer | Sortformer Diarization (CoreML) | ~240 MB | aufklarer/Sortformer-Diarization-CoreML |
API Swift
import SpeechVAD
let pipeline = try await DiarizationPipeline.fromPretrained()
let result = pipeline.diarize(audio: samples, sampleRate: 16000)
for seg in result.segments {
print("Speaker \(seg.speakerId): [\(seg.startTime)s - \(seg.endTime)s]")
}
// Extracción de hablante objetivo
let targetEmb = pipeline.embeddingModel.embed(audio: enrollmentAudio, sampleRate: 16000)
let segments = pipeline.extractSpeaker(
audio: meetingAudio, sampleRate: 16000,
targetEmbedding: targetEmb
)