Técnico

Diarização local de falantes: como criamos um assistente de notas de reunião 100% privado

O OpenWhispr é o assistente de reuniões open source em que seu áudio nunca sai do seu dispositivo. Cada impressão vocal, cada rótulo de falante, cada embedding — processados e armazenados na sua própria máquina.

OpenWhispr

OpenWhispr

Engenharia

15 de abril de 2026
Índice

Diarização local de falantes é o processo de rotular quem falou quando em uma gravação de reunião, executado inteiramente no dispositivo do usuário, sem transmitir áudio, embeddings ou transcrições para qualquer servidor externo. Transcrições de reunião têm o problema do “quem disse o quê”. O Zoom entrega um bloco de texto. A maioria dos assistentes de notas com IA — Otter, Fireflies, Read, Granola — resolve isso enviando o áudio da sua reunião para os servidores deles, executando um modelo de falantes lá e devolvendo segmentos rotulados. Sua voz, e as vozes dos seus colegas, acabam como vetores no banco de dados de outra pessoa. Nós não queríamos isso, e nossos usuários também não. O OpenWhispr é o assistente de notas de reunião open source construído sobre a premissa oposta: o áudio nunca sai do seu dispositivo, as impressões vocais ficam em um arquivo SQLite local no seu disco, e nada sobre uma reunião é enviado a menos que você escolha isso explicitamente. Este artigo é a análise técnica do lado de diarização dessa promessa — o que ela é, quais modelos escolhemos, como eles se encaixam e exatamente onde cada byte de dados fica em cada etapa.

Última atualização: 15 de abril de 2026. Os detalhes de implementação se referem ao recurso de diarização de falantes incorporado ao app desktop do OpenWhispr em 14 de abril de 2026. Toda afirmação técnica abaixo vem de referências primárias ou pode ser inspecionada diretamente no repositório open source.

Resumo de verificação (fontes duplas)

O que a diarização de falantes realmente é

Diarização de falantes é o processo de rotular quem falou quando em uma gravação — não o que foi dito. A transcrição responde o quê; a diarização responde quem. As duas são complementares: um bom assistente de notas de reunião precisa de ambas, e fazê-las funcionar juntas é mais difícil do que qualquer uma sozinha.

A diarização é realmente difícil por alguns motivos teimosos. Duas vozes podem se sobrepor na mesma chamada. Ruído de fundo entra junto. Falas curtas (“sim”, “ok”) carregam quase nenhuma informação de falante. Novos falantes aparecem no meio de uma reunião. E, ao contrário da transcrição, a diarização precisa tomar decisões globais — ela não consegue rotular o segmento sete sem compará-lo com o segmento dois. Esse contexto global é o motivo pelo qual a maioria das soluções comerciais faz a diarização no servidor, sobre a gravação completa, e depois devolve a transcrição rotulada.

Transcrição sozinha não basta. Uma reunião de dez pessoas com quarenta e cinco minutos de interrupções cruzadas, renderizada como um bloco de texto indiferenciado, é ilegível. Notas de reunião — o entregável que as pessoas de fato querem — exigem atribuição de falantes, porque itens de ação, decisões e compromissos estão todos ligados a pessoas. “Alice vai entregar a migração até sexta-feira” é uma nota. “Vai entregar a migração até sexta-feira” é ruído.

Diarização não é a mesma coisa que reconhecimento de falante

A diarização diz “estes três trechos são o Falante A; estes dois são o Falante B”. Ela não sabe quem é o Falante A. O reconhecimento de falante coloca um nome no Falante A ao comparar uma impressão vocal com um perfil armazenado. O OpenWhispr faz os dois — a diarização roda em toda reunião, e o reconhecimento entra em ação depois que você rotula alguém pelo nome. Ambos permanecem locais.

O pipeline local em quatro etapas

O pipeline de diarização de falantes do OpenWhispr tem quatro etapas: detecção de atividade vocal, segmentação, embedding de falante e clustering — seguidas por uma etapa de mesclagem que reconcilia segmentos de falantes com a transcrição. Cada etapa é um modelo ONNX separado ou um binário nativo. Sem runtime Python, sem PyTorch, sem CUDA. O tamanho total dos modelos em disco é de cerca de 45MB.

O pipeline de diarização local

Captura de áudioMicrofone + sistema, 16kHz
VADSilero, 2MB
Segmentaçãopyannote 3.0
IncorporaçãoCAM++, 512-dim
AgrupamentoAgglomerative, 0,5
RótulosCorresponder perfis em SQLite

Por que permanece local:

Cada estágio é executado no seu dispositivo por meio de um único binário sherpa-onnx — sem Python, sem nuvem.

~45MB de modelos ONNX baixados uma vez e armazenados em cache em ~/.cache/openwhispr/diarization-models/.

Incorporações são vetores 512-dim float32 armazenados como SQLite BLOBs em seu disco local.

A captura de áudio é de fluxo duplo desde o primeiro dia. O microfone, por definição, é “você”, então nunca gastamos processamento tentando diarizar sua própria voz — cada segmento do microfone é rotulado como você pela fonte, não por correspondência de voz. O áudio do sistema (todas as outras pessoas na chamada) é a única coisa que realmente precisa ser separado por falante, o que já corta o trabalho pela metade.

Depois que o áudio é capturado, as quatro etapas rodam em sequência. A VAD remove o silêncio para que a etapa cara de embedding nunca desperdice ciclos com frames vazios. A segmentação corta a fala contínua em trechos de um único falante. O modelo de embedding transforma cada trecho em uma impressão vocal de 512 dimensões. O clustering aglomerativo agrupa impressões parecidas, produzindo um conjunto final de IDs de falantes. Uma última passada mescla esses IDs de volta na transcrição por sobreposição de timestamps, para que cada palavra termine marcada com um falante.

Tudo roda dentro de um único processo filho criado pelo app — sem servidor, sem porta em escuta, sem IPC para qualquer coisa fora do Electron. Você pode confirmar isso lendo src/helpers/diarization.js no repositório open source.

Etapa 1: detecção de atividade vocal (Silero)

A detecção de atividade vocal (VAD) é o filtro barato que separa fala de silêncio antes da execução das etapas caras. Executar um modelo de embedding de falante sobre silêncio desperdiça CPU e produz vetores inúteis. A VAD é o que mantém o restante do pipeline rápido.

Usamos Silero VAD: um modelo ONNX de 2MB, licenciado sob MIT, que na prática é o padrão open source da indústria. O Silero retorna uma probabilidade por frame de que a janela atual de 32 milissegundos contenha fala. Em uma CPU moderna, o custo é desprezível — cerca de 0,1 milissegundo por bloco de 32 milissegundos, ou aproximadamente um terço de um por cento de um núcleo.

O pipeline ao vivo executa o Silero continuamente no áudio do sistema durante uma chamada. Os limiares que realmente entregamos são ajustados para áudio real de reuniões, não para um benchmark limpo:

  • Tamanho da janela: 512 amostras (32 ms a 16 kHz)
  • Limiar de fala: 0,15 — deliberadamente agressivo para não perder falantes baixos ou distantes
  • Limiar de silêncio: 0,08
  • O segmento termina após: 16 janelas silenciosas consecutivas (~512 ms)
  • Segmento mínimo para embedding: 0,8 segundo
  • Cadência de identificação ao vivo: a cada 1 segundo depois que ≥1,6 segundo de fala foi acumulado

Compromisso honesto

Um limiar de fala agressivo de 0,15 captura pessoas falando baixo em microfones distantes, mas também dispara falsamente de vez em quando com teclas barulhentas ou ruído do ambiente. O filtro de segmento mínimo de 0,8 segundo descarta a maior parte desses falsos positivos antes que cheguem à etapa de embedding, protegendo a precisão nas etapas seguintes.

Etapa 2: segmentação (pyannote 3.0)

A segmentação pega fala contínua e a corta em trechos que contêm apenas um falante cada, detectando tanto pontos de troca de falante quanto regiões de sobreposição. É a etapa que decide onde estão os limites entre falantes. Tudo depois dela assume que esses limites estão corretos.

Usamos pyannote-segmentation-3.0, a versão atual do modelo da equipe pyannote.audio, exportada para ONNX pelo projeto sherpa-onnx . Pyannote é a referência acadêmica de fato: o pipeline pyannote.audio alcança taxas de erro de diarização na faixa de 12–15% em benchmarks padrão como AMI e CALLHOME — os mesmos números que provedores comerciais de nuvem gostam de citar.

Por que o port ONNX especificamente? Porque distribuir PyTorch dentro de um app Electron é inviável — só o runtime tem cerca de 1,5GB e, na prática, exige uma GPU para ser rápido. O export ONNX é um único arquivo de 6,6MB que roda em CPU via ONNX Runtime. O mesmo modelo, com uma fração mínima do peso.

Nós o invocamos pelo binário de diarização offline do sherpa-onnx com alguns flags. As configurações de duração mínima vêm dos padrões recomendados no artigo do pyannote: rajadas curtas de fala são mescladas ao silêncio adjacente, e silêncios curtos não quebram um segmento contínuo de falante. A saída é uma lista de pares de tempo inicial/final marcados com IDs provisórios de falantes ( speaker_0, speaker_1, etc.).

Etapa 3: embeddings de falante (CAM++)

Um embedding de falante é uma lista de números de tamanho fixo que representa uma voz do mesmo modo que um hash representa um arquivo. Duas gravações da mesma voz produzem embeddings próximos no espaço vetorial; vozes diferentes produzem embeddings distantes. “Próximo” é medido por similaridade de cosseno — um único número entre -1 e 1. Esse é o coração matemático de todo o restante do pipeline.

Nosso modelo de embedding é o CAM++ do projeto 3D-Speaker da DAMO Academy, da Alibaba, treinado no benchmark de verificação de falantes VoxCeleb . O arquivo específico que distribuímos é 3dspeaker_speech_campplus_sv_en_voxceleb_16k.onnx, um export ONNX de cerca de 28MB que produz um vetor float32 de 512 dimensões por segmento. O artigo é arXiv:2303.00332 (Wang et al., 2023).

Escolhemos CAM++ em vez da alternativa óbvia, ECAPA-TDNN, por três motivos concretos: cerca de metade dos parâmetros, menor taxa de erro igual no VoxCeleb1-O e inferência em CPU mais rápida. O modelo MSDD do NVIDIA NeMo não tem export ONNX oficial e depende muito de PyTorch. O Picovoice Falcon usa licença comercial por assento. O framework CoreML Speech da Apple é exclusivo para macOS. CAM++ foi a única opção que satisfez nossas restrições — multiplataforma, ONNX, licença aberta e rápido em CPU.

Para alimentar o CAM++, distribuímos nosso próprio extrator de características de banco de filtros log-mel em src/helpers/speakerEmbeddings.js: 80 bandas mel, janelas de 25 milissegundos, saltos de 10 milissegundos, parâmetros padrão para essa classe de modelo. As características fluem para o modelo ONNX via onnxruntime-node, rodando no processo principal do Electron. Do outro lado sai um Float32Array de 512 entradas, que gravamos no SQLite como um BLOB de 2.048 bytes.

A etapa de correspondência em si é a coisa mais simples possível: similaridade de cosseno entre dois vetores. Este é o código literal que decide se dois segmentos de reunião são do mesmo falante:

// src/helpers/speakerEmbeddings.js
cosineSimilarity(a, b) {
  let dot = 0, normA = 0, normB = 0;
  for (let i = 0; i < a.length; i++) {
    dot += a[i] * b[i];
    normA += a[i] * a[i];
    normB += b[i] * b[i];
  }
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
  return denom === 0 ? 0 : dot / denom;
}

Por que 512 dimensões?

512 é o ponto de equilíbrio atual no ranking de verificação de falantes VoxCeleb entre precisão e custo de armazenamento. 256 dimensões começam a perder poder discriminativo em vozes parecidas, mas diferentes; 1024 dimensões adicionam armazenamento e computação com retornos decrescentes. 512 float32s × 2KB × mil reuniões com dez falantes são 20MB em disco. Isso cabe confortavelmente em um arquivo SQLite local.

Etapa 4: clustering — o problema de “quantos falantes?”

Depois de gerar embeddings para todos os segmentos, você tem N vetores. O clustering os agrupa em K falantes — mas a parte difícil é que geralmente você não sabe K de antemão. Uma ligação de vendas pode ter 2 falantes. Uma reunião geral pode ter 8. Clustering com K fixo quebra nas duas pontas.

Usamos clustering aglomerativo com um limiar de similaridade de cosseno de 0,5: começamos com cada segmento como seu próprio cluster, mesclamos os dois clusters com maior similaridade e repetimos até que nenhum par restante ultrapasse o limiar. O limiar, não uma contagem-alvo, decide onde parar. Isso escala naturalmente de dois a dez falantes sem nenhuma configuração.

A invocação real fica em src/helpers/diarization.js. É um spawn do binário sherpa-onnx com alguns flags de CLI:

// src/helpers/diarization.js
const args = [
  `--segmentation.pyannote-model=${segPath}`,
  `--embedding.model=${embPath}`,
  `--clustering.num-clusters=${numSpeakers}`,    // -1 = auto
  `--clustering.cluster-threshold=${threshold}`, // 0.5 default
  "--min-duration-on=0.2",
  "--min-duration-off=0.5",
  wavPath,
];

A saída é texto simples: uma linha por segmento, formatada como start_sec -- end_sec speaker_NN. Analisamos isso em um array de objetos e depois percorremos a transcrição, associando cada segmento de transcrição ao segmento de diarização com maior sobreposição temporal. Segmentos vindos do microfone são sempre rotulados you — a fonte vence a voz, sempre.

O limiar de 0,5 não é chute. Nós o ajustamos contra nosso próprio harness de avaliação (scripts/meeting-diarization-eval.js) em gravações reais de reuniões. Limiares mais altos subclusterizam — um falante é dividido em dois. Limiares mais baixos superclusterizam — duas vozes semelhantes são mescladas. 0,5 foi o ponto ideal ao qual chegamos.

Diarização ao vivo: rótulos enquanto você fala

A diarização apenas em batch é precisa, mas invisível durante uma chamada. Apenas ao vivo é imediato, mas ruidoso. O OpenWhispr executa as duas: rótulos ao vivo aparecem durante a reunião, e uma passada completa em batch os refina depois.

O caminho ao vivo fica em src/helpers/liveSpeakerIdentifier.js. Ele captura o fluxo de áudio do sistema a 16 kHz, alimenta cada frame de 32 milissegundos no Silero VAD e acumula frames de fala até que um segmento seja fechado (após ~512 ms de silêncio) ou atinja a cadência de identificação ao vivo (a cada 1 segundo depois de acumular ≥1,6 segundo). Quando dispara, ele extrai um embedding CAM++, compara contra o mapa em memória de embeddings da chamada ativa e perfis de falantes armazenados, e emite um evento IPC — meeting-speaker-identified — com a melhor correspondência. O lado React recebe isso e atualiza a bolha da transcrição no lugar.

Depois que uma pessoa define manualmente o nome de um falante na UI, essa atribuição fica bloqueada. A passada batch pós-reunião não tem permissão para sobrescrevê-la, mesmo que sua própria análise discorde. Esse comportamento de bloqueio fica em src/utils/transcriptSpeakerState.ts, e ele importa: a pior UX possível é deixar uma passada automatizada sobrescrever silenciosamente a correção de um usuário.

Quando a gravação para, executamos a diarização offline completa no arquivo WAV inteiro. O batch tem contexto completo — ele vê a reunião inteira, não apenas os últimos 1,6 segundo — e produz uma rotulagem final mais limpa. O resultado batch é reconciliado com os rótulos ao vivo, respeita todos os bloqueios do usuário e substitui palpites provisórios pelo agrupamento mais preciso.

Por que o híbrido vence qualquer uma das opções sozinha

Apenas ao vivo é rápido, mas ruidoso em segmentos curtos. Apenas batch é preciso, mas silencioso até a reunião terminar. O híbrido dá os dois — feedback instantâneo durante a chamada e uma transcrição final que reflete a análise com contexto completo. O custo é uma etapa extra de reconciliação, executada uma vez em segundo plano e efetivamente gratuita.

Perfis de voz: lembrando os palestrantes nas reuniões

Depois que você identifica uma pessoa como “Alice” em uma reunião, o OpenWhispr passa a rotulá-la automaticamente em reuniões futuras — sem que você faça mais nada e sem que a impressão digital de voz dela saia do seu dispositivo.

O mecanismo é simples: o embedding centróide de cada falante é armazenado no seu banco SQLite local. Quando novos embeddings aparecem em uma chamada futura, nós os comparamos com cada perfil salvo usando similaridade de cosseno. Uma correspondência acima do limite atribui o nome automaticamente. Nada disso toca um servidor. As três tabelas que alimentam esse fluxo são SQLite puro:

-- src/helpers/database.js
CREATE TABLE speaker_profiles (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  display_name TEXT NOT NULL,
  email TEXT,
  embedding BLOB NOT NULL,          -- 512 float32s (~2KB)
  sample_count INTEGER DEFAULT 1,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE speaker_mappings (
  note_id INTEGER NOT NULL,
  speaker_id TEXT NOT NULL,
  profile_id INTEGER,
  display_name TEXT NOT NULL,
  PRIMARY KEY (note_id, speaker_id),
  FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE,
  FOREIGN KEY (profile_id) REFERENCES speaker_profiles(id) ON DELETE SET NULL
);

CREATE TABLE note_speaker_embeddings (
  note_id INTEGER NOT NULL,
  speaker_id TEXT NOT NULL,
  embedding BLOB NOT NULL,
  PRIMARY KEY (note_id, speaker_id),
  FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE
);

A correspondência é de três níveis, para equilibrar a confiança e os falsos positivos:

  • Cosseno ≥ 0,70: confirmação automática. O rótulo aparece imediatamente sem aviso.
  • 0,55 ≤ Cosseno < 0,70: sugerir. A IU mostra “Esta é Alice?” com controles de confirmar/dispensar e aguarda sua entrada.
  • Cosseno <0,55: permaneça anônimo. O segmento permanece como Falante N até que você os nomeie manualmente.

Quando você confirma uma correspondência, o centróide armazenado é atualizado por meio de uma média em execução, em vez de uma substituição bruta. A fórmula é simples: new_avg = (stored_avg × sample_count + new_embedding) / (sample_count + 1). Isso lida com desvios de voz – uma manhã fria, um microfone diferente, um cabo ruim – sem interromper partidas futuras. Quanto mais amostras da voz de Alice o sistema tiver visto, mais estável se tornará seu perfil.

Novos perfis também acionam reetiquetagem retroativa. Quando você nomeia um falante pela primeira vez, uma tarefa em segundo plano percorre os embeddings armazenados de todas as reuniões anteriores, encontra correspondências e atualiza falantes antes sem nome nas transcrições históricas. Mapeamentos travados pelo usuário nunca são alterados. O resultado: nomear Alice uma vez a rotula retroativamente em todas as chamadas anteriores em que ela participou.

Se o Google Agenda estiver conectado e a reunião tiver participantes, a interface do seletor de falantes já vem preenchida com nomes e e-mails. Um clique mapeia Falante 2 alice@example.come persiste para sempre — os participantes e os perfis de voz permanecem vinculados, para que os nomes corretos apareçam mesmo em todos os dispositivos.

Onde seus dados realmente residem

O áudio nunca sai do seu dispositivo. Isso não é uma frase de marketing — é uma propriedade mecânica do código e, como o OpenWhispr é de código aberto, você pode verificar por conta própria. Aqui está exatamente o que acontece com cada dado em cada etapa.

O queOnde moraSai do seu aparelho?
Áudio bruto da reuniãoDiretório temporário do sistema operacional, excluído após diarizaçãoNão - nunca
Modelos de diarização ONNX~/.cache/openwhispr/diarization-models/Baixado uma vez na primeira inicialização e nunca mais
Embeddings de falante (impressões digitais de voz)BLOB SQLite localNão - nunca
Mapeamentos de falante para nomeTabela SQLite localNão - nunca
Texto transcritoSQLite local (sincronização na nuvem opcional se você ativá-lo)Somente se você aceitar

O áudio PCM bruto de uma reunião é gravado em um arquivo no diretório temporário do sistema operacional, entregue ao binário sherpa-onnx gerado localmente e excluído em um bloco finallyquando a diarização termina. Não há nenhum caminho de código no pipeline de diarização do OpenWhispr que abra um socket de rede para fazer upload de áudio. Como o código é aberto, você pode usar grep para procurar src/helpers/diarization.js, src/helpers/liveSpeakerIdentifier.js, e src/helpers/speakerEmbeddings.js for fetch, axios, ou http e confirme você mesmo.

O IPC entre os processos principal e de renderização do Electron usa o recurso integrado contextBridge — um canal no nível do kernel dentro de um único processo, não um soquete de rede. Embeddings de falantes e perfis de voz são armazenados como BLOBs SQLite no mesmo arquivo databaseque suas notas, com as mesmas permissões de arquivo no nível do sistema operacional.

Uma exceção, transparentemente

A única atividade de rede neste recurso é o download único dos modelos na primeira inicialização: três arquivos ONNX obtidos do CDN de releases do sherpa-onnx no GitHub. Documentamos isso explicitamente porque “uma chamada de rede durante a configuração, zero depois” conta uma história de privacidade muito diferente de “inferência na nuvem sempre ativa”; como assistente de reuniões de código aberto, preferimos dizer isso claramente em vez de deixar você descobrir depois.

Limitações honestas

A diarização local é boa, não perfeita. Aqui estão os casos em que há realmente dificuldades e o que fazemos em relação a cada um deles.

  • Falantes sobrepostos. Duas pessoas conversando ao mesmo tempo apresentam perdas inerentes para atribuição de rótulo único. O Pyannote 3.0 detecta a região de sobreposição, mas um único speaker_id por segmento não é suficiente para representar “ambas as pessoas conversando”. Marcamos esses segmentos como provisórios e permitimos que você os corrija na IU.
  • Precisão de partida a frio. A primeira reunião com um novo colega usa apenas os recursos genéricos treinados em VoxCeleb. Depois de rotulá-los uma vez e o perfil ter algumas amostras, as reuniões subsequentes serão visivelmente mais precisas.
  • Declarações curtas. “Sim”, “ok”, uma risada rápida - qualquer coisa abaixo de aproximadamente 0,8 segundos não pode produzir uma incorporação confiável. Retiramos esses segmentos do estágio de incorporação e voltamos para a propagação de rótulos do contexto circundante.
  • Áudio de campo distante ou baixo SNR. Alguém ao telefone com viva-voz em uma sala barulhenta se degradará graciosamente, mas visivelmente. O controle automático de ganho e o ajuste VAD ajudam, mas nenhuma sofisticação do modelo supera completamente a física ruim.
  • Inferência somente CPU. Uma GPU seria mais rápida, mas deliberadamente não exigimos uma. Na prática, o rendimento em uma CPU moderna é bom: aproximadamente 100 milissegundos por incorporação e cerca de 30 segundos de diarização em lote para uma reunião de 45 minutos em um Mac M1.

Publicamos nosso equipamento de avaliação ( (scripts/meeting-diarization-eval.js) ) no repositório de código aberto para que você possa medir tudo isso sozinho, em suas próprias gravações - não em nossos clipes de demonstração escolhidos a dedo. Isso parece mais honesto do que um número brilhante em uma apresentação de marketing.

Por que escolhemos Sherpa-ONNX

Precisávamos de um tempo de execução de diarização que fosse multiplataforma, livre de Python, rápido em CPU e de código aberto. Sherpa-ONNX foi a única opção que acertou todos os quatro.

Nossos requisitos eram concretos. Precisava rodar em macOS (Intel e Apple Silicon), Windows x64 e Linux x64 sem caminhos de código separados. Não podia empacotar o PyTorch: só o runtime tem cerca de 1,5 GB e, na prática, exige uma GPU para ser utilizável em latências interativas. Não podia exigir CUDA. E a licença precisava ser permissiva o suficiente para ser distribuída em um app de desktop comercial, mas de código aberto.

Aqui está o que realmente avaliamos:

  • pyannote.audio (Python): descartado. Agrupar PyTorch em um aplicativo Electron é um fracasso.
  • NVIDIA NeMo MSDD: Dependente de PyTorch, sem exportação ONNX primária, centrada em GPU.
  • Picovoice Falcon: licença comercial por usuário ativo mais ativação na nuvem na primeira execução.
  • Estrutura Apple CoreML + Speech: Apenas macOS, sem paridade entre plataformas.
  • sherpa-onnx: Apache 2.0, binário ONNX nativo, ~35 MB de modelos, interface spawn-and-read-stdout, mantido ativamente por k2-fsa. O mesmo conjunto de ferramentas alimenta a produção ASR em outros produtos de remessa.

Uma ressalva honesta: o binário de diarização offline de falantes do sherpa-onnx é relativamente novo. Fixamos a versão em v1.12.23e execute nosso equipamento de avaliação completo em cada atualização antes de acertar o pino. Essa é a compensação certa para algo tão resistente.

Perguntas frequentes

Existe um anotador de reunião privada que não carrega meu áudio para a nuvem?
Sim. O OpenWhispr é um anotador de reuniões gratuito e de código aberto em que seu áudio nunca sai do seu dispositivo. A transcrição roda localmente com OpenAI Whisper ou NVIDIA Parakeet, a diarização de falantes roda localmente com segmentação pyannote e embeddings CAM++, e as impressões digitais de voz ficam armazenadas em um arquivo SQLite local na sua própria máquina — não em nossos servidores.
Otter, Fireflies ou Granola podem ver minhas reuniões?
Sim. Anotadores de reunião baseados em nuvem enviam o áudio da reunião para seus servidores para transcrever e gerar notas. Rótulos de falantes, transcrições e embeddings de voz são calculados na infraestrutura deles e armazenados em seus bancos de dados. Se você precisa de uma alternativa que mantenha cada byte de áudio no seu dispositivo, precisa de uma ferramenta local como o OpenWhispr.
O que é diarização de falantes?
A diarização de falantes é o processo de rotular quem falou e quando em uma gravação, diferentemente da transcrição, que responde ao que foi dito. Uma transcrição com diarização mostra cada expressão marcada com um rótulo de falante – Alice, Bob, Falante 3 – em vez de uma parede de texto indiferenciada.
A diarização do locutor local realmente funciona ou é um downgrade da nuvem?
Funciona. O pipeline do OpenWhispr — pyannote-segmentation-3.0 mais embeddings CAM++ do projeto 3D-Speaker, executados via sherpa-onnx — atinge taxas de erro de diarização na mesma faixa de APIs comerciais em nuvem em benchmarks padrão. As principais limitações honestas são lidar com falas sobrepostas e a precisão em cold start para vozes que o sistema nunca ouviu antes.
Quanto espaço em disco os modelos de diarização local usam?
Cerca de 45 MB no total: segmentação pyannote (6,6 MB), o modelo de embedding de falantes CAM++ (28 MB) e Silero VAD (2 MB). Os três são baixados uma vez na primeira inicialização e armazenados em cache em ~/.cache/openwhispr/diarization-models/. Depois disso, permanecem em disco e não são baixados novamente.
Preciso de uma GPU para executar a diarização local do OpenWhispr?
Não. Tudo roda em CPU via ONNX Runtime. Os Apple Silicon modernos e os CPUs x86 recentes lidam com isso confortavelmente – na prática, cerca de 30 segundos de diarização em lote para uma reunião de 45 minutos em um Mac M1, com rótulos de falante ao vivo aparecendo dentro de um ou dois segundos durante a própria chamada.
O OpenWhispr é realmente de código aberto?
Sim. O código de diarização, o pipeline de transcrição e o restante do app de desktop são abertos no GitHub. As afirmações de privacidade deste artigo são diretamente verificáveis: use grep nos auxiliares de diarização por fetch, axios ou http e você não encontrará chamadas de rede no caminho de diarização.
O que acontece quando duas pessoas conversam durante uma chamada?
O Pyannote 3.0 detecta e anota explicitamente regiões de sobreposição, mas a atribuição de rótulo único durante a sobreposição é inerentemente com perdas. Marcamos esses segmentos como provisórios na transcrição e permitimos que você os corrija clicando no rótulo do orador. Suas correções manuais são bloqueadas e nunca são substituídas por passes automatizados posteriores.

Experimente o anotador de reuniões privadas

O OpenWhispr é um assistente de reuniões gratuito e de código aberto em que seu áudio nunca sai do seu dispositivo. Diarização local, transcrição local, perfis de voz locais. Funciona em macOS, Windows e Linux.

Não é necessária uma conta · Funciona off-line · Código aberto para sempre