Diarisation locale des locuteurs : comment nous avons construit un preneur de notes de réunion 100 % privé
OpenWhispr est l'assistant de réunion open source où votre audio ne quitte jamais votre appareil. Chaque empreinte vocale, chaque étiquette de locuteur, chaque embedding — traités et stockés sur votre propre machine.
OpenWhispr
Ingénierie
Table des matières
La diarisation locale des locuteurs consiste à indiquer qui a parlé et quand dans un enregistrement de réunion, entièrement sur l'appareil de l'utilisateur, sans transmettre audio, embeddings ni transcriptions à un serveur externe. Les transcriptions de réunions ont un problème de « qui a dit quoi ». Zoom vous donne un mur de texte. La plupart des preneurs de notes IA — Otter, Fireflies, Read, Granola — le résolvent en téléversant l'audio de votre réunion sur leurs serveurs, en y exécutant un modèle de locuteurs, puis en renvoyant des segments étiquetés. Votre voix, et celles de vos collègues, finissent sous forme de vecteurs dans la base de données de quelqu'un d'autre. Nous ne voulions pas de ça, et nos utilisateurs non plus. OpenWhispr est le preneur de notes de réunion open source construit sur l'hypothèse inverse : l'audio ne quitte jamais votre appareil, les empreintes de locuteurs vivent dans un fichier SQLite local sur votre disque, et rien d'une réunion n'est téléversé sauf si vous le choisissez explicitement. Cet article est l'analyse technique du volet diarisation de cette promesse — ce que c'est, les modèles que nous avons choisis, comment ils s'assemblent et où vit exactement chaque octet de données à chaque étape.
Dernière mise à jour : 15 avril 2026. Les détails d'implémentation font référence à la fonctionnalité de diarisation des locuteurs fusionnée dans l'application de bureau OpenWhispr le 14 avril 2026. Chaque affirmation technique ci-dessous provient de références primaires ou peut être inspectée directement dans le dépôt open source.
Synthèse de vérification (sources doubles)
- La diarisation des locuteurs répond à « qui a parlé quand ». Elle est évaluée sur des benchmarks communautaires comme NIST Rich Transcription et le pipeline de référence pyannote.audio. évaluation NIST RT · article pyannote.audio (Bredin et al., 2020)
- Nous utilisons pyannote-segmentation-3.0 et CAM++ du projet 3D-Speaker. Les deux sont open source, portables vers ONNX, et téléchargés directement depuis les releases de modèles sherpa-onnx. carte de modèle pyannote-segmentation-3.0 · article CAM++ / 3D-Speaker (Wang et al., 2023)
- Toute l'inférence passe par sherpa-onnx, un binaire ONNX natif — aucun runtime Python, aucun PyTorch, aucun GPU requis. Nous verrouillons la version du binaire et de ses dépendances au moment du build. sherpa-onnx sur GitHub · releases sherpa-onnx
- Les empreintes vocales sont des vecteurs float32 à 512 dimensions stockés sous forme de BLOB SQLite locaux, environ 2KB par locuteur. Elles ne quittent jamais l'appareil. documentation des types SQLite · ONNX Runtime
- Aucun audio, aucun embedding et aucune transcription ne sont transmis hors de l'appareil pendant la diarisation. La seule activité réseau de toute la fonctionnalité est le téléchargement unique des modèles au premier lancement, que vous pouvez inspecter vous-même dans le dépôt open source. politique de confidentialité d'OpenWhispr · OpenWhispr sur GitHub
Ce qu'est vraiment la diarisation des locuteurs
La diarisation des locuteurs consiste à indiquer qui a parlé et quand dans un enregistrement — pas ce qui a été dit. La transcription répond au quoi ; la diarisation répond au qui. Les deux sont complémentaires : un bon preneur de notes de réunion a besoin des deux, et les faire fonctionner ensemble est plus difficile que l'un ou l'autre pris isolément.
La diarisation est réellement difficile pour quelques raisons tenaces. Deux voix peuvent se chevaucher dans le même appel. Le bruit de fond s'infiltre. Les énoncés courts (« oui », « ok ») contiennent presque aucune information de locuteur. De nouveaux locuteurs arrivent au milieu d'une réunion. Et contrairement à la transcription, la diarisation doit prendre des décisions globales — elle ne peut pas étiqueter le segment sept sans le comparer au segment deux. C'est ce contexte global qui pousse la plupart des solutions commerciales à faire la diarisation côté serveur sur l'enregistrement complet, puis à renvoyer la transcription étiquetée.
La transcription seule ne suffit pas. Une réunion de dix personnes avec quarante-cinq minutes d'interruptions, rendue comme un mur de texte indifférencié, est illisible. Les notes de réunion — le livrable que les gens veulent vraiment — nécessitent l'attribution des locuteurs, car les actions, décisions et engagements sont tous attachés à des personnes. « Alice livrera la migration vendredi » est une note. « Livrera la migration vendredi » est du bruit.
La diarisation n'est pas la même chose que la reconnaissance du locuteur
La diarisation dit « ces trois segments sont le locuteur A ; ces deux-là sont le locuteur B ». Elle ne sait pas qui est le locuteur A. La reconnaissance du locuteur met un nom sur le locuteur A en comparant une empreinte vocale à un profil stocké. OpenWhispr fait les deux — la diarisation s'exécute sur chaque réunion, la reconnaissance intervient une fois que vous avez nommé quelqu'un. Les deux restent locales.
Le pipeline local en quatre étapes
Le pipeline de diarisation des locuteurs d'OpenWhispr comporte quatre étapes : détection d'activité vocale, segmentation, embedding de locuteur et clustering — suivies d'une étape de fusion qui réconcilie les segments de locuteurs avec la transcription. Chaque étape est un modèle ONNX distinct ou un binaire natif. Aucun runtime Python, aucun PyTorch, aucun CUDA. L'empreinte totale des modèles sur disque est d'environ 45MB.
Le pipeline de diarisation locale
Pourquoi cela reste local :
Chaque étape s'exécute sur votre appareil via un seul binaire sherpa-onnx — pas de Python, pas de cloud.
~45MB des modèles ONNX téléchargés une fois, puis mis en cache sur ~/.cache/openwhispr/diarization-models/.
Les intégrations sont des vecteurs 512-dim float32 stockés sous SQLite BLOBs sur votre disque local.
La capture audio est à double flux dès le départ. Le microphone, par définition, c'est « vous » ; nous ne dépensons donc jamais de calcul à essayer de diariser votre propre voix — chaque segment micro reçoit l'étiquette vous par sa source, pas par correspondance vocale. L'audio système (toutes les autres personnes de l'appel) est le seul flux qui a réellement besoin d'être découpé par locuteur, ce qui divise immédiatement le travail par deux.
Une fois l'audio capturé, les quatre étapes s'exécutent en séquence. La VAD supprime les silences pour que l'étape coûteuse d'embedding ne gaspille jamais de cycles sur des trames vides. La segmentation découpe la parole continue en segments à locuteur unique. Le modèle d'embedding transforme chaque segment en une empreinte vocale à 512 dimensions. Le clustering agglomératif regroupe les empreintes qui se ressemblent et produit un ensemble final d'ID de locuteurs. Un dernier passage fusionne ces ID dans la transcription par chevauchement d'horodatages, afin que chaque mot soit associé à un locuteur.
Le tout s'exécute dans un seul processus enfant lancé par l'app — pas de serveur, pas de port d'écoute, pas d'IPC vers quoi que ce soit en dehors d'Electron. Vous pouvez le confirmer en lisant src/helpers/diarization.js dans le dépôt open source.
Étape 1 : détection d'activité vocale (Silero)
La détection d'activité vocale (VAD) est le filtre peu coûteux qui sépare la parole du silence avant l'exécution des étapes coûteuses. Exécuter un modèle d'embedding de locuteur sur du silence gaspille du CPU et produit des vecteurs inutilisables. La VAD est ce qui garde le reste du pipeline rapide.
Nous utilisons Silero VAD: un modèle ONNX de 2MB, sous licence MIT, qui est devenu de fait le standard open source de l'industrie. Silero renvoie, pour chaque trame, la probabilité que la fenêtre courante de 32 millisecondes contienne de la parole. Sur un CPU moderne, le coût est négligeable — environ 0,1 milliseconde par segment de 32 millisecondes, soit à peu près un tiers de pour cent d'un cœur.
Le pipeline en direct exécute Silero en continu sur l'audio système pendant un appel. Les seuils que nous livrons réellement sont ajustés pour de l'audio de réunion réel, pas pour un benchmark propre :
- Taille de fenêtre : 512 échantillons (32 ms à 16 kHz)
- Seuil de parole : 0,15 — volontairement agressif pour ne pas manquer les locuteurs calmes ou éloignés
- Seuil de silence : 0,08
- Le segment se termine après : 16 fenêtres silencieuses consécutives (~512 ms)
- Segment minimum pour l'embedding : 0,8 seconde
- Cadence d'identification en direct : toutes les 1 seconde une fois ≥1,6 seconde de parole accumulée
Compromis honnête
Un seuil de parole agressif de 0,15 capte les voix faibles sur des micros éloignés, mais il déclenche aussi parfois à tort sur des frappes de clavier bruyantes ou du bruit de pièce. Le filtre de segment minimum de 0,8 seconde élimine la plupart de ces faux positifs avant qu'ils n'atteignent l'étape d'embedding, ce qui protège la précision en aval.
Étape 2 : segmentation (pyannote 3.0)
La segmentation prend une parole continue et la découpe en segments contenant chacun un seul locuteur, en détectant à la fois les points de changement de locuteur et les zones de chevauchement. C'est l'étape qui décide où se trouvent les frontières entre locuteurs. Tout ce qui vient ensuite suppose que ces frontières sont correctes.
Nous utilisons pyannote-segmentation-3.0, la version actuelle du modèle de l'équipe pyannote.audio, exportée en ONNX par le projet sherpa-onnx . Pyannote est la référence académique de fait : le pipeline pyannote.audio atteint des taux d'erreur de diarisation de l'ordre de 12 à 15 % sur des benchmarks standard comme AMI et CALLHOME — les mêmes chiffres que les fournisseurs cloud commerciaux aiment citer.
Pourquoi le port ONNX précisément ? Parce qu'embarquer PyTorch dans une app Electron est irréaliste — le runtime seul fait environ 1,5GB et exige en pratique un GPU pour être rapide. L'export ONNX est un seul fichier de 6,6MB qui s'exécute sur CPU via ONNX Runtime. Même modèle, fraction minuscule du poids.
Nous l'invoquons via le binaire de diarisation hors ligne de sherpa-onnx avec quelques flags. Les paramètres de durée minimale viennent des valeurs par défaut recommandées dans l'article pyannote : les courts éclats de parole sont fusionnés avec le silence adjacent, et les silences courts ne cassent pas un segment de locuteur continu. La sortie est une liste de paires début/fin étiquetées avec des ID de locuteurs provisoires ( speaker_0, speaker_1, etc.).
Étape 3 : embeddings de locuteur (CAM++)
Un embedding de locuteur est une liste de nombres de longueur fixe qui représente une voix comme un hash représente un fichier. Deux enregistrements de la même voix produisent des embeddings proches dans l'espace vectoriel ; des voix différentes produisent des embeddings éloignés. La notion de « proche » se mesure par similarité cosinus — un seul nombre entre -1 et 1. C'est le cœur mathématique de tout le reste du pipeline.
Notre modèle d'embedding est CAM++ du projet 3D-Speaker d'Alibaba DAMO Academy, entraîné sur le benchmark de vérification de locuteurs VoxCeleb . Le fichier précis que nous livrons est 3dspeaker_speech_campplus_sv_en_voxceleb_16k.onnx, un export ONNX d'environ 28MB qui produit un vecteur float32 à 512 dimensions par segment. L'article est arXiv:2303.00332 (Wang et al., 2023).
Nous avons choisi CAM++ plutôt que l'alternative évidente, ECAPA-TDNN, pour trois raisons concrètes : environ moitié moins de paramètres, un taux d'erreur égal plus faible sur VoxCeleb1-O et une inférence CPU plus rapide. Le modèle MSDD de NVIDIA NeMo n'a pas d'export ONNX officiel et dépend fortement de PyTorch. Picovoice Falcon implique une licence commerciale par utilisateur actif. Le framework CoreML Speech d'Apple est réservé à macOS. CAM++ était la seule option qui satisfaisait nos contraintes — multiplateforme, ONNX, licence ouverte, rapide sur CPU.
Pour alimenter CAM++, nous livrons notre propre extracteur de caractéristiques par banc de filtres log-mel dans src/helpers/speakerEmbeddings.js : 80 bandes mel, fenêtres de 25 millisecondes, sauts de 10 millisecondes, paramètres standard pour cette classe de modèle. Les caractéristiques entrent dans le modèle ONNX via onnxruntime-node, qui s'exécute dans le processus principal d'Electron. En sortie, on obtient un Float32Array de 512 entrées, que nous écrivons dans SQLite sous forme de BLOB de 2 048 octets.
L'étape de correspondance elle-même est la plus simple possible : similarité cosinus entre deux vecteurs. Voici le code exact qui décide si deux segments de réunion correspondent au même locuteur :
// 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;
}Pourquoi 512 dimensions ?
512 est aujourd'hui le meilleur compromis sur le classement de vérification de locuteurs VoxCeleb entre précision et coût de stockage. 256 dimensions commencent à perdre du pouvoir discriminant sur des voix proches mais différentes ; 1024 dimensions ajoutent stockage et calcul avec des rendements décroissants. 512 float32 × 2KB × mille réunions de dix locuteurs représentent 20MB sur disque. Cela tient confortablement dans un fichier SQLite local.
Étape 4 : clustering — le problème du « combien de locuteurs ? »
Après avoir créé un embedding pour chaque segment, vous avez N vecteurs. Le clustering les regroupe en K locuteurs — mais la partie difficile est que, le plus souvent, vous ne connaissez pas K à l'avance. Un appel commercial peut avoir 2 locuteurs. Une réunion générale peut en avoir 8. Le clustering à K fixe casse aux deux extrêmes.
Nous utilisons un clustering agglomératif avec un seuil de similarité cosinus de 0,5 : on commence avec chaque segment dans son propre cluster, on fusionne les deux clusters les plus similaires, puis on répète jusqu'à ce qu'aucune paire restante ne dépasse le seuil. C'est le seuil, et non un nombre cible, qui décide où s'arrêter. Cela s'adapte naturellement de deux à dix locuteurs sans configuration.
L'invocation réelle se trouve dans src/helpers/diarization.js. C'est un spawn du binaire sherpa-onnx avec quelques flags 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,
];La sortie est du texte brut : une ligne par segment, formatée comme start_sec -- end_sec speaker_NN. Nous l'analysons en tableau d'objets, puis parcourons la transcription en associant chaque segment de transcription au segment de diarisation qui présente le plus grand chevauchement temporel. Les segments provenant du micro sont toujours étiquetés you — la source bat la voix, à chaque fois.
Le seuil de 0,5 n'est pas une supposition. Nous l'avons ajusté avec notre propre banc d'évaluation (scripts/meeting-diarization-eval.js) sur de vrais enregistrements de réunions. Des seuils plus élevés sous-clusterisent — un locuteur est divisé en deux. Des seuils plus bas sur-clusterisent — deux voix similaires sont fusionnées. 0,5 est le bon compromis auquel nous sommes arrivés.
Diarisation en direct : des étiquettes pendant que vous parlez
La diarisation uniquement en batch est précise mais invisible pendant un appel. Le direct seul est immédiat mais bruité. OpenWhispr exécute les deux : des étiquettes en direct apparaissent pendant la réunion, puis un passage batch complet les affine ensuite.
Le chemin en direct se trouve dans src/helpers/liveSpeakerIdentifier.js. Il capture le flux audio système à 16 kHz, envoie chaque trame de 32 millisecondes dans Silero VAD et accumule les trames de parole jusqu'à ce qu'un segment se ferme (après ~512 ms de silence) ou atteigne la cadence d'identification en direct (toutes les 1 seconde une fois ≥1,6 seconde accumulée). Lors du déclenchement, il extrait un embedding CAM++, le compare à la carte en mémoire des embeddings de l'appel actif et aux profils de locuteurs stockés, puis émet un événement IPC — meeting-speaker-identified — avec la meilleure correspondance. Le côté React le récupère et met à jour la bulle de transcription sur place.
Dès qu'un humain a défini manuellement le nom d'un locuteur dans l'UI, cette assignation est verrouillée. Le passage batch post-réunion n'a pas le droit de l'écraser, même si sa propre analyse n'est pas d'accord. Ce comportement de verrouillage se trouve dans src/utils/transcriptSpeakerState.ts, et il compte : la pire UX possible serait de laisser un passage automatisé écraser silencieusement la correction d'un utilisateur.
Quand l'enregistrement s'arrête, nous lançons la diarisation hors ligne complète sur le fichier WAV entier. Le batch a le contexte complet — il voit toute la réunion, pas seulement les 1,6 dernières secondes — et produit un étiquetage final plus propre. Le résultat batch est réconcilié avec les étiquettes en direct, respecte chaque verrou utilisateur et remplace les suppositions provisoires par un regroupement plus précis.
Pourquoi l'hybride bat chaque approche seule
Le direct seul est rapide mais bruité sur les segments courts. Le batch seul est précis mais silencieux jusqu'à la fin de la réunion. L'hybride vous donne les deux — un retour instantané pendant l'appel et une transcription finale qui reflète l'analyse en contexte complet. Le coût est une étape de réconciliation supplémentaire, exécutée une seule fois en arrière-plan et pratiquement gratuite.
Profils vocaux : reconnaître les locuteurs d'une réunion à l'autre
Dès que vous étiquetez un locuteur « Alice » dans une réunion, OpenWhispr l'étiquette automatiquement dans chaque réunion future — sans action de votre part et sans que son empreinte vocale ne quitte jamais votre appareil.
Le mécanisme est simple : l'embedding centroïde de chaque locuteur est stocké dans votre base SQLite locale. Quand de nouveaux embeddings apparaissent lors d'un futur appel, nous les comparons par similarité cosinus à chaque profil stocké. Une correspondance au-dessus du seuil assigne automatiquement le nom. Rien de tout cela ne touche un serveur. Les trois tables qui l'alimentent sont du SQLite ordinaire :
-- 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
);La correspondance se fait sur trois niveaux, pour équilibrer confiance et faux positifs :
- Cosinus ≥ 0,70 : confirmation automatique. L'étiquette apparaît immédiatement, sans invite.
- 0,55 ≤ cosinus < 0,70 : suggestion. L'UI affiche « Est-ce Alice ? » avec des contrôles confirmer / ignorer, puis attend votre action.
- Cosinus < 0,55 : reste anonyme. Le segment reste Locuteur N jusqu'à ce que vous le nommiez manuellement.
Lorsque vous confirmez une correspondance, le centroïde stocké se met à jour via une moyenne glissante plutôt que par écrasement brut. La formule est simple : new_avg = (stored_avg × sample_count + new_embedding) / (sample_count + 1). Cela absorbe la dérive vocale — un matin froid, un micro différent, un mauvais câble — sans casser les correspondances futures. Plus le système a vu d'échantillons de la voix d'Alice, plus son profil devient stable.
Les nouveaux profils déclenchent aussi un réétiquetage rétroactif. Lorsque vous nommez un locuteur pour la première fois, une tâche en arrière-plan parcourt les embeddings stockés de toutes les réunions passées, trouve les correspondances et met à jour les locuteurs auparavant non nommés dans les transcriptions historiques. Les associations verrouillées par l'utilisateur ne sont jamais touchées. Résultat : nommer Alice une fois l'étiquette rétroactivement dans chaque appel précédent auquel elle a participé.
Si Google Calendar est connecté et que la réunion a des participants, l'UI de sélection de locuteur se préremplit avec leurs noms et e-mails. Un clic associe Locuteur 2 → alice@example.com et le conserve pour toujours — les participants et les profils vocaux restent liés, afin que les bons noms apparaissent même d'un appareil à l'autre.
Où vivent réellement vos données
L'audio ne quitte jamais votre appareil. Ce n'est pas une phrase marketing — c'est une propriété mécanique du code, et comme OpenWhispr est open source, vous pouvez le vérifier vous-même. Voici exactement ce qui arrive à chaque type de donnée à chaque étape.
| Quoi | Où c'est stocké | Quitte votre appareil ? |
|---|---|---|
| Audio brut de la réunion | Répertoire temporaire de l'OS, supprimé après la diarisation | Non — jamais |
| Modèles ONNX de diarisation | ~/.cache/openwhispr/diarization-models/ | Téléchargés une fois au premier lancement, puis plus jamais |
| Embeddings de locuteurs (empreintes vocales) | BLOB SQLite local | Non — jamais |
| Correspondances locuteur-nom | Table SQLite locale | Non — jamais |
| Texte de transcription | SQLite local (synchronisation cloud optionnelle si vous l'activez) | Uniquement si vous l'autorisez |
L'audio PCM brut d'une réunion est écrit dans un fichier du répertoire temporaire de votre OS, transmis au binaire sherpa-onnx lancé localement, puis supprimé dans un bloc finallyquand la diarisation se termine. Aucun chemin de code dans le pipeline de diarisation d'OpenWhispr n'ouvre de socket réseau pour téléverser l'audio. Comme le code est open source, vous pouvez grep src/helpers/diarization.js, src/helpers/liveSpeakerIdentifier.js, et src/helpers/speakerEmbeddings.js pour fetch, axios, ou http et le confirmer vous-même.
L'IPC entre les processus main et renderer d'Electron utilise le contextBridge intégré — un pipe au niveau du noyau dans un seul processus, pas une socket réseau. Les embeddings de locuteurs et les profils vocaux sont stockés comme BLOB SQLite dans le même fichier de base de données que vos notes, avec les mêmes permissions de fichier au niveau de l'OS.
Une exception, en toute transparence
La seule activité réseau de cette fonctionnalité est le téléchargement unique des modèles au premier lancement — trois fichiers ONNX récupérés depuis le CDN des releases GitHub de sherpa-onnx. Nous le documentons explicitement parce que « un appel réseau pendant la configuration, zéro ensuite » raconte une tout autre histoire de confidentialité que « inférence cloud toujours active », et comme assistant de réunion open source, nous préférons vous le dire plutôt que vous laisser le découvrir.
Limites honnêtes
La diarisation locale est bonne, pas parfaite. Voici les cas où elle a réellement du mal, et ce que nous faisons pour chacun.
- Locuteurs qui se chevauchent. Deux personnes qui parlent en même temps créent forcément une perte pour une attribution à étiquette unique. Pyannote 3.0 détecte la zone de chevauchement, mais un seul speaker_id par segment ne suffit pas à représenter « deux personnes parlent ». Nous marquons ces segments comme provisoires et vous laissons les corriger dans l'UI.
- Précision à froid. La première réunion avec un nouveau collègue utilise uniquement les caractéristiques génériques entraînées sur VoxCeleb. Une fois que vous l'avez étiqueté une fois et que le profil contient quelques échantillons, les réunions suivantes sont nettement plus précises.
- Énoncés courts. « Oui », « ok », un rire bref — tout ce qui dure moins de ~0,8 seconde ne peut pas produire un embedding fiable. Nous excluons ces segments de l'étape d'embedding et revenons à une propagation d'étiquette à partir du contexte voisin.
- Audio lointain ou faible SNR. Quelqu'un sur haut-parleur de téléphone dans une pièce bruyante dégradera le résultat progressivement, mais visiblement. Le contrôle automatique du gain et l'ajustement de la VAD aident, mais aucune sophistication de modèle ne peut complètement compenser une mauvaise physique.
- Inférence CPU uniquement. Un GPU serait plus rapide, mais nous choisissons délibérément de ne pas l'exiger. En pratique, le débit sur un CPU moderne est correct : ~100 millisecondes par embedding, et environ 30 secondes de diarisation batch pour une réunion de 45 minutes sur un Mac M1.
Nous publions notre banc d'évaluation ( (scripts/meeting-diarization-eval.js) ) dans le dépôt open source pour que vous puissiez mesurer tout cela vous-même, sur vos propres enregistrements — pas sur nos extraits de démonstration soigneusement choisis. Cela nous semble plus honnête qu'un chiffre brillant dans un deck marketing.
Pourquoi nous avons choisi Sherpa-ONNX
Nous avions besoin d'un runtime de diarisation multiplateforme, sans Python, rapide sur CPU et open source. Sherpa-ONNX était la seule option à remplir les quatre critères.
Nos exigences étaient concrètes. Il devait fonctionner sur macOS (Intel et Apple Silicon), Windows x64 et Linux x64 sans chemins de code séparés. Il ne pouvait pas embarquer PyTorch — le runtime seul fait environ 1,5GB et exige essentiellement un GPU pour être utilisable avec des latences interactives. Il ne pouvait pas exiger CUDA. Et la licence devait être suffisamment permissive pour être distribuée dans une app desktop commerciale mais open source.
Voici ce que nous avons réellement évalué :
- pyannote.audio (Python): écarté. Embarquer PyTorch dans une app Electron est rédhibitoire.
- NVIDIA NeMo MSDD: dépendant de PyTorch, sans export ONNX officiel, centré sur le GPU.
- Picovoice Falcon: licence commerciale par utilisateur actif, plus activation cloud au premier lancement.
- Apple CoreML + Speech framework: macOS uniquement, aucune parité multiplateforme.
- sherpa-onnx: Apache 2.0, binaire ONNX natif, ~35MB de modèles, interface spawn-and-read-stdout, maintenu activement par k2-fsa. La même chaîne d'outils alimente de l'ASR de production dans d'autres produits distribués.
Une réserve honnête : le binaire de diarisation de locuteurs hors ligne de sherpa-onnx est relativement récent. Nous verrouillons la version sur v1.12.23 et exécutons notre banc d'évaluation complet sur chaque mise à jour avant de relever ce verrou. C'est le bon compromis pour quelque chose d'aussi critique.
Questions fréquentes
- Existe-t-il un preneur de notes de réunion privé qui ne téléverse pas mon audio dans le cloud ?
- Oui. OpenWhispr est un preneur de notes de réunion gratuit et open source où votre audio ne quitte jamais votre appareil. La transcription s'exécute localement avec OpenAI Whisper ou NVIDIA Parakeet, la diarisation des locuteurs s'exécute localement avec la segmentation pyannote et les embeddings CAM++, et les empreintes vocales sont stockées dans un fichier SQLite local sur votre propre machine — pas sur nos serveurs.
- Otter, Fireflies ou Granola peuvent-ils voir mes réunions ?
- Oui. Les preneurs de notes IA basés sur le cloud téléversent l'audio de vos réunions vers leurs serveurs afin de le transcrire et de le diariser. Les étiquettes de locuteurs, les transcriptions et les embeddings vocaux sont calculés dans leur infrastructure et stockés dans leurs bases de données. Si vous avez besoin d'une alternative qui garde chaque octet d'audio sur votre appareil, il vous faut un outil local-first comme OpenWhispr.
- Qu'est-ce que la diarisation des locuteurs ?
- La diarisation des locuteurs consiste à indiquer qui a parlé quand dans un enregistrement, à la différence de la transcription, qui répond à ce qui a été dit. Une transcription diarizée affiche chaque énoncé avec une étiquette de locuteur — Alice, Bob, Locuteur 3 — au lieu d'un mur de texte indifférencié.
- La diarisation locale des locuteurs fonctionne-t-elle vraiment, ou est-ce une régression par rapport au cloud ?
- Elle fonctionne. Le pipeline d'OpenWhispr — pyannote-segmentation-3.0 plus les embeddings CAM++ du projet 3D-Speaker, exécutés via sherpa-onnx — atteint des taux d'erreur de diarisation du même ordre que les API cloud commerciales sur les benchmarks standard. Les principaux compromis honnêtes concernent la gestion des locuteurs qui se chevauchent et la précision à froid sur les voix que le système n'a jamais entendues.
- Combien d'espace disque utilisent les modèles de diarisation locaux ?
- Environ 45MB au total : segmentation pyannote (6,6MB), modèle d'embedding de locuteur CAM++ (28MB) et Silero VAD (2MB). Les trois sont téléchargés une fois au premier lancement et mis en cache dans ~/.cache/openwhispr/diarization-models/. Ensuite, ils restent sur le disque et ne sont jamais retéléchargés.
- Ai-je besoin d'un GPU pour exécuter la diarisation locale d'OpenWhispr ?
- Non. Tout s'exécute sur CPU via ONNX Runtime. Les Apple Silicon modernes et les CPU x86 récents le gèrent confortablement — en pratique, environ 30 secondes de diarisation batch pour une réunion de 45 minutes sur un Mac M1, avec des étiquettes de locuteurs en direct qui apparaissent en une ou deux secondes pendant l'appel lui-même.
- OpenWhispr est-il réellement open source ?
- Oui. Le code de diarisation, le pipeline de transcription et le reste de l'app desktop sont open source sur GitHub. Les affirmations de confidentialité de cet article sont directement vérifiables : grep les helpers de diarisation pour fetch, axios ou http et vous ne trouverez aucun appel réseau dans le chemin de diarisation.
- Que se passe-t-il quand deux personnes parlent en même temps pendant un appel ?
- Pyannote 3.0 détecte et annote explicitement les zones de chevauchement, mais l'attribution à étiquette unique pendant un chevauchement perd forcément de l'information. Nous marquons ces segments comme provisoires dans la transcription et vous laissons les corriger en cliquant sur l'étiquette du locuteur. Vos corrections manuelles sont verrouillées et ne sont jamais écrasées par des passes automatisées ultérieures.
Essayez le preneur de notes de réunion privé
OpenWhispr est un assistant de réunion gratuit et open source où votre audio ne quitte jamais votre appareil. Diarisation locale, transcription locale, profils vocaux locaux. Fonctionne sur macOS, Windows et Linux.
Aucun compte requis · Fonctionne hors ligne · Open source pour toujours