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).
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.
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
| Opcao | Descricao |
|---|---|
--target-speaker | Audio de enrolamento para extracao de falante alvo (somente pyannote) |
--embedding-engine | Engine de embedding de falante: mlx ou coreml (somente pyannote) |
--vad-filter | Pre-filtrar com Silero VAD (somente pyannote) |
--rttm | Saida no formato RTTM |
--json | Formato de saida JSON |
--score-against | Arquivo RTTM de referencia para avaliacao DER |
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:
| Componente | Modelo | Tamanho | HuggingFace |
|---|---|---|---|
| Segmentacao | Pyannote-Segmentation-3.0 | ~5.7 MB | aufklarer/Pyannote-Segmentation-MLX |
| Embedding de falante | WeSpeaker-ResNet34-LM (MLX) | ~25 MB | aufklarer/WeSpeaker-ResNet34-LM-MLX |
| Embedding de falante | 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
)