Diarizacao de falantes

Identifique quem falou quando em uma gravacao multi-locutor. Dois engines de diarizacao estao disponiveis: um pipeline de duas etapas Pyannote (segmentacao + encadeamento de locutores baseado em atividade, depois embedding post-hoc) e um modelo ponta-a-ponta Sortformer (CoreML, Neural Engine).

Engines

Selecione o engine com --engine pyannote (padrao) ou --engine sortformer.

Pyannote (padrao)

Pipeline de duas etapas: a segmentacao Pyannote processa janelas sobrepostas com encadeamento de locutores baseado em atividade (correlacao de Pearson nas zonas de sobreposicao) para atribuir rotulos globais de locutor. A extracao post-hoc de embedding WeSpeaker permite identificacao de locutor alvo via audio de enrolamento.

Sortformer (CoreML)

Modelo neural ponta-a-ponta de diarizacao da NVIDIA. Preve diretamente a atividade por frame de ate 4 falantes sem estagios separados de embedding ou clustering. Roda no Neural Engine via CoreML com buffers de estado de streaming (FIFO + speaker cache).

Nota

Sortformer nao produz embeddings de falante. As flags --target-speaker e --embedding-engine estao disponiveis apenas com o engine Pyannote.

Pipeline Pyannote

O pipeline padrao roda em duas etapas:

Etapa 1: Segmentacao + Encadeamento de falantes

Pyannote segmentation-3.0 processa janelas deslizantes de 10 segundos com sobreposicao de 50%. Um powerset decoder converte a saida de 7 classes em probabilidades por falante (ate 3 falantes locais por janela). Janelas adjacentes compartilham uma sobreposicao de 5 segundos — a identidade do falante e propagada entre janelas computando correlacao de Pearson entre as trilhas de probabilidade na zona de sobreposicao, com matching exclusivo greedy para IDs de falante globais consistentes.

Etapa 2: Embedding post-hoc

Apos a diarizacao, o WeSpeaker ResNet34-LM extrai um embedding centroide de 256 dimensoes por falante. Esses embeddings permitem extracao de falante alvo (--target-speaker) mas nao guiam a atribuicao de falante em si.

Migrando do pyannote.audio

Se voce vem da biblioteca Python pyannote.audio — substituindo uma subclasse de Pipeline que define pipeline.segmentation = ..., ou migrando de um servidor que hospeda pyannote/speaker-diarization-3.1 — o Soniqo encapsula o mesmo modelo Pyannote-Segmentation-3.0 e o executa totalmente no dispositivo em Apple Silicon. Sem runtime Python, sem CUDA, sem token Hugging Face em tempo de 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 = ... (subclasse personalizada) Fixo: Pyannote-Segmentation-3.0 (MLX ou CoreML, selecionado automaticamente)
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

Os pesos do Pyannote-Segmentation-3.0 sao convertidos do checkpoint upstream do HuggingFace, portanto os logits de segmentacao sao numericamente equivalentes dentro da tolerancia de precisao em ponto flutuante. O encadeamento pos-segmentacao (correlacao de Pearson em janelas sobrepostas + matching exclusivo guloso) e as etapas de embedding pos-hoc com WeSpeaker sao reimplementados em Swift, mas produzem saida RTTM comparavel ao pipeline Python de referencia.

Ainda nao suportado

Nao ha um equivalente em streaming para OnlineSpeakerDiarization no motor Pyannote. Para diarizacao em tempo real, use --engine sortformer, que executa o modelo Sortformer com buffers de estado FIFO + cache de falantes.

Uso do 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

Extracao de falante alvo

Forneca audio de enrolamento de um locutor conhecido para extrair apenas seus segmentos de uma gravacao. O pipeline computa o embedding de falante do audio de enrolamento e encontra o cluster com a maior similaridade de cosseno.

# Extract segments for a specific speaker
.build/release/speech diarize meeting.wav --target-speaker enrollment.wav

Pontuacao DER

Avalie a qualidade da diarizacao pontuando contra um arquivo RTTM de referencia. O pipeline calcula a Diarization Error Rate (DER), que mede a proporcao de tempo atribuido incorretamente.

# Score against reference RTTM
.build/release/speech diarize meeting.wav --score-against reference.rttm

Saida RTTM

A flag --rttm produz saida Rich Transcription Time Marked, um formato padrao usado para avaliacao de diarizacao. Cada linha segue o formato:

SPEAKER filename 1 start_time duration <NA> <NA> speaker_id <NA> <NA>

Opcoes

OpcaoDescricao
--target-speakerAudio de enrolamento para extracao de falante alvo (somente pyannote)
--embedding-engineEngine de embedding de falante: mlx ou coreml (somente pyannote)
--vad-filterPre-filtrar com Silero VAD (somente pyannote)
--rttmSaida no formato RTTM
--jsonFormato de saida JSON
--score-againstArquivo RTTM de referencia para avaliacao DER
Importante

A diarizacao funciona melhor com gravacoes que tem turnos claros de falante. Fala altamente sobreposta pode reduzir a precisao. O numero de falantes e determinado automaticamente.

Downloads de modelos

Os modelos sao baixados automaticamente no primeiro uso:

ComponenteModeloTamanhoHuggingFace
SegmentacaoPyannote-Segmentation-3.0~5.7 MBaufklarer/Pyannote-Segmentation-MLX
Embedding de falanteWeSpeaker-ResNet34-LM (MLX)~25 MBaufklarer/WeSpeaker-ResNet34-LM-MLX
Embedding de falanteWeSpeaker-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]")
}

// Target speaker extraction
let targetEmb = pipeline.embeddingModel.embed(audio: enrollmentAudio, sampleRate: 16000)
let segments = pipeline.extractSpeaker(
    audio: meetingAudio, sampleRate: 16000,
    targetEmbedding: targetEmb
)