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).

Nota

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.

Aún no soportado

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ónDescripción
--target-speakerAudio de enrolamiento para la extracción de un hablante objetivo (solo pyannote)
--embedding-engineMotor de embedding de hablante: mlx o coreml (solo pyannote)
--vad-filterPre-filtrado con Silero VAD (solo pyannote)
--rttmSalida en formato RTTM
--jsonFormato de salida JSON
--score-againstArchivo RTTM de referencia para evaluación DER
Importante

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:

ComponenteModeloTamañoHuggingFace
SegmentaciónPyannote-Segmentation-3.0~5,7 MBaufklarer/Pyannote-Segmentation-MLX
Embedding de hablanteWeSpeaker-ResNet34-LM (MLX)~25 MBaufklarer/WeSpeaker-ResNet34-LM-MLX
Embedding de hablanteWeSpeaker-ResNet34-LM (CoreML)~25 MBaufklarer/WeSpeaker-ResNet34-LM-CoreML
SortformerSortformer Diarization (CoreML)~240 MBaufklarer/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
)