Diarización de hablantes
Identifica quién habló cuándo en una grabación con varios hablantes. Hay tres motores: Pyannote ligero y predeterminado, Community-1 con pipeline completo (CoreML + PLDA/VBx nativo) y Sortformer para streaming.
Motores
Selecciona --engine pyannote (por defecto), --engine community1 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.
Community-1 (CoreML + VBx nativo)
El bundle Community-1 CoreML usa 10 s de contexto, paso de 1 s, embeddings WeSpeaker enmascarados, PLDA/VBx nativo y asignación restringida; devuelve un centroide de 256 dimensiones por hablante. La comprobación de paridad en cinco archivos obtuvo 4,66% DER / 21,43% JER.
Con el evaluador por cuadrícula de frames de speech-swift sobre el mismo subconjunto, Community-1 obtuvo 9,31% DER / 16,20% JER, 25,5× tiempo real y 1,54 GB de RSS máximo; Pyannote, el motor predeterminado, obtuvo 5,03% / 9,28%, 105,7× tiempo real y 393 MB. Community-1 es por tanto un pipeline opcional de paridad con upstream que ofrece límites de hablantes y centroides, no una mejora universal de precisión o velocidad. Los resultados de los dos métodos de evaluación no son directamente comparables.
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).
Para audio en vivo, SortformerStreamingSession ejecuta incrementalmente el modelo Sortformer .streaming con búferes PCM mono de 16 kHz de cualquier tamaño. Cuando dispone de sus 560 ms de anticipación, avanza en pasos de audio de 480 ms; una prueba de cinco archivos en el Neural Engine de la serie M midió una latencia mediana de 12,4 ms por push de 500 ms. Las etiquetas de hablante siguen el Arrival-Order Speaker Cache en vez de reagrupar cada ventana, y cada push devuelve una instantánea de todo el flujo. finish() procesa la cola almacenada. En la prueba de equivalencia con modelo, los segmentos finales coincidieron con la diarización .streaming de búfer completo dentro de aproximadamente un frame de 80 ms.
Sortformer no produce embeddings de hablante. Los flags --target-speaker y --embedding-engine solo están disponibles con el motor Pyannote.
Baseline publicado de CALLHOME
La tabla muestra el baseline publicado por NVIDIA de diarización sin identidad para diar_streaming_sortformer_4spk-v2. Es una referencia externa, no una medición local de la conversión CoreML de speech-swift ni un resultado de atribución a hablantes con nombre.
| Dataset | Condición | DER |
|---|---|---|
| CALLHOME Part 2 | 2 hablantes de referencia | 6,57% |
| CALLHOME Part 2 | 3 hablantes de referencia | 10,05% |
| CALLHOME Part 2 | 4 hablantes de referencia | 12,44% |
Todas las filas usan 1,04 s de latencia de búfer de entrada, incluyen solapamientos, aplican un collar de 0,25 s y postprocesado ajustado en CALLHOME Part 1, que es disjunto. CALLHOME requiere licencia y no se incluye. El diarization-bench local presenta resultados comparables por número de hablantes de referencia, tanto en consola como en byReferenceSpeakerCount del JSON.
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
# Basic diarization (pyannote, default)
.build/release/speech diarize meeting.wav
# End-to-end Sortformer (CoreML)
.build/release/speech diarize meeting.wav --engine sortformer
# RTTM output format (for evaluation)
.build/release/speech diarize meeting.wav --rttm
# JSON output
.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.
# Extract segments for a specific speaker
.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.
# Score against reference RTTM
.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]")
}
// Target speaker extraction
let targetEmb = pipeline.embeddingModel.embed(audio: enrollmentAudio, sampleRate: 16000)
let segments = pipeline.extractSpeaker(
audio: meetingAudio, sampleRate: 16000,
targetEmbedding: targetEmb
)