Built the throwaway prototype and sorb tested it: keyboard gone, voice natural, at 35 percent rather than the 100 percent the spec assumed as default. That settles the control question — the package exposes setSuppressionLevel and the wasm carries atten_lim, so DeepFilterNet limits attenuation natively and the spec's dry/wet mix drops out entirely, taking its missing delay node and phase problem with it. Measured what the spec had guessed: 23.27 MB per client, an order of magnitude above RNNoise. Also found that the package fetches model and wasm from a third-party CDN at call time, which a self-hosted platform cannot accept — the asset URL is configurable and the prototype already serves them locally, so that path is proven rather than assumed. Still open and decision-relevant: CPU figures, the phone, and the standing cost of carrying this through every upstream rebase. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8.5 KiB
type, id, status, created, milestone, priority, area, related
| type | id | status | created | milestone | priority | area | related | |
|---|---|---|---|---|---|---|---|---|
| issue | 0054 | open | 2026-08-15 | M4 | medium | element |
|
Tastaturgeräusche in Calls: quelloffene KI-Geräuschunterdrückung im Client prüfen
Problem
Der WebRTC-Standardfilter (noiseSuppression) ist auf stationäres Rauschen ausgelegt
(Lüfter, Netzbrummen). Transiente Geräusche — Tastaturanschläge — rutschen durch: sie
haben eine sehr schnelle Anstiegszeit und ein unvorhersehbares Spektrum, sodass die
laufende Rauschprofil-Schätzung sie nicht als Störung erkennt. Betroffen sind ausdrücklich
auch leise Chiclet-Tastaturen (MacBook), nicht nur mechanische.
Grundlage ist eine externe Architekturspezifikation (Gemini Deep Research, 2026-07-29):
client-seitige KI-Filterung via WebAssembly, eingehängt über das LiveKit-TrackProcessor-
Interface, mit Intensitätsregler in den Audio-Einstellungen.
Bewertung der Spezifikation
Trägt
- Diagnose stimmt. Stationär vs. transient ist die richtige Erklärung dafür, warum die vorhandenen Toggles nicht helfen.
- Client-seitig ist der richtige Ort — und kein Widerspruch zur bisherigen Linie.
threadnet-call:docs/axion1337-fork.md§5 verwirft ML-Rauschunterdrückung server-seitig (LiveKit Agents), weil es dort keinen unterstützten Weg gibt, bereinigtes Audio an andere Teilnehmer weiterzureichen. Genau dieser Einwand greift client-seitig nicht. Die Spezifikation setzt die alte Entscheidung fort, statt ihr zu widersprechen. - AudioWorklet statt ScriptProcessor, eigener hochpriorer Audio-Thread: richtig und nicht verhandelbar.
- Der 128-vs-480-Sample-Mismatch (Web Audio liefert 128er-Blöcke, die Modelle brauchen 480) und der nötige Ringpuffer sind sauber benannt — daran scheitern naive Umsetzungen.
- Chromium-AudioWorklet-Leak und die Gegenmaßnahme (eigener
AudioContext, hart schließen) sind real und richtig adressiert. - Wasm SIMD ist tatsächlich Voraussetzung, nicht Optimierung.
Trägt nicht
- ⚠️ Konkreter Fehler: die Latenzkompensation fehlt. Der finale Dry/Wet-Code mischt das
unverzögerte Original mit dem ~40 ms verzögerten KI-Signal. Das erzeugt Kammfilter und
Phasenauslöschung — hörbar als blechernes Echo, also genau das Gegenteil des Ziels. Ein
früherer Entwurf im selben Gespräch hatte dafür einen
DelayNode; in der Endfassung ist er verschwunden. Das ist kein Detail. - Der Dry/Wet-Ansatz ist konzeptionell fragwürdig. Die Begründung („neuronale Netze kennen nur An/Aus") ist für DeepFilterNet falsch — es hat einen nativen Parameter zur Begrenzung der Dämpfung. „Weniger aggressiv" heißt richtig: das Modell weniger dämpfen lassen. Dry/Wet mischt stattdessen ungefiltertes Signal zurück — inklusive der Tastaturanschläge, die man loswerden wollte.
- Die Zahlen taugen nicht als Entscheidungsgrundlage. PESQ „RNNoise ~3.88" gegen „DFN3 3.5–4.34": die untere DFN3-Grenze läge unter RNNoise. Werte aus verschiedenen Testsets, nicht vergleichbar.
- Bundle-Größe geschätzt, nicht gemessen („15–25 MB"). Für eine Browser-App, die beim Call-Start lädt, ist das der kritische Wert überhaupt — muss gemessen werden.
- Die genannten NPM-Pakete sind Experimente (
deepfilternet3-worker-test,…-noise-filter-trong). Die Spec empfiehlt selbst, aus dem Rust-Quellcode zu bauen — dann gehört ehrlich dazu: wir übernehmen eine Rust/wasm-Toolchain in die Build-Kette. - Mobil fehlt. Element Call läuft auf Telefonen; DFN3 auf einem Mittelklasse-Android ist offen und wird mit „läuft auf modernen Prozessoren" abgetan.
getUserMedia-Constraints fehlen im Code. Wer selbst filtert, muss die Browser-eigenenoiseSuppressionabschalten (sonst arbeiten zwei Filter gegeneinander) undechoCancellationerhalten. Im Gespräch erwähnt, im finalen Code verschwunden.- Erzwungene 48 kHz ohne Fallback — Geräte mit 44,1 kHz brauchen einen Pfad.
- ⚠️ Der größte Posten fehlt ganz: Fork-Wartung. Das wäre eine erhebliche
Eigenentwicklung in
threadnet-call, die bei jedem Upstream-Rebase mitgeschleppt und inaxion1337-fork.mdgepflegt werden muss. Die Spec erwähnt das mit keinem Wort. - Lizenz nur behauptet. DeepFilterNet-Code ist MIT/Apache-2.0 — die Modellgewichte sind separat zu prüfen, bevor „null Lizenzkosten" behauptet wird.
Vorgeschlagenes Vorgehen (vor jeder Zeile Produktivcode)
- Messen statt annehmen. Reproduzierbarer A/B-Test mit mechanischer und Chiclet-Tastatur gegen die heutigen Toggles — belegt das Problem und liefert die Referenz für „besser".
- Wegwerf-Prototyp außerhalb des Forks. DFN3 als Wasm auf einer eigenen Testseite: Bundle-Größe, CPU und Latenz auf echten Geräten messen (inkl. Telefon). Erst diese Zahlen entscheiden über Modell und Machbarkeit.
- Regler über den Modellparameter, nicht über Dry/Wet. Falls doch Dry/Wet: Delay-Node zur Latenzkompensation ist Pflicht.
- Dann erst Integration als
TrackProcessorund Eintrag inaxion1337-fork.md.
Offen (Entscheidung sorb)
Ob der Aufwand lohnt. Die Plattform hat derzeit einen sehr kleinen Nutzerkreis; dem steht eine dauerhaft zu pflegende Fork-Anpassung mit Rust/wasm-Build gegenüber. Die Alternative — Tastaturgeräusche als hinnehmbar erklären und stattdessen Push-to-Talk bzw. bewusstes Stummschalten dokumentieren — ist billiger und sollte bewusst verworfen werden, nicht übersehen.
Prototyp gebaut und getestet 2026-08-15
Wegwerf-Aufbau außerhalb des Forks (deepfilternet3-noise-filter v1.3.0 + livekit-client,
lokale Testseite, Assets selbst ausgeliefert). Ziel war, die drei ungeklärten Punkte der
Spezifikation durch Messung zu ersetzen.
Ergebnis: es funktioniert — und zwar besser als nötig
Höreindruck sorb: „die Tastatur ist weg, Stimme klingt natürlich" — bei 35 % Dämpfung.
Das ist der wichtigste Einzelbefund, und er entscheidet die Reglerfrage:
- Der Dry/Wet-Mix der Spezifikation entfällt ersatzlos. Das Paket bietet
setSuppressionLevel(), und die wasm-Signatur trägtatten_lim— DeepFilterNet begrenzt die Dämpfung nativ. Die Prämisse der Spec („neuronale Netze kennen nur An/Aus") ist widerlegt. Damit entfallen zugleich der fehlende Delay-Node und das Phasenproblem — es gibt gar keinen zweiten Signalpfad mehr, der phasenversetzt zurückgemischt werden müsste. - 35 % statt 100 % als Vorgabe. Die Spec setzt „standardmäßig 100 % Filter-Aktivität"; gemessen reicht gut ein Drittel für „Tastatur weg und Stimme natürlich". Weniger Dämpfung heißt weniger Artefaktrisiko — der Standardwert sollte bei ~35 % liegen, nicht am Anschlag.
Gemessen (ersetzt die Schätzungen der Spec)
| Größe | Wert | Bemerkung |
|---|---|---|
| Download je Client | 23,27 MB | 15,66 MB df_bg.wasm + 7,61 MB Modell — Spec-Schätzung („15–25 MB") bestätigt, oberer Rand |
| Vergleich RNNoise | 2,0–4,6 MB | rund ein Zehntel (@jitsi/rnnoise-wasm, @shiguredo/rnnoise-wasm) |
| Lizenz Paket | Apache-2.0 oder MIT | wie behauptet; Modellgewichte kommen aus dem DeepFilterNet-Projekt |
⚠️ Befund, der die Paketwahl bestimmt: fremdes CDN
deepfilternet3-noise-filter lädt Modell und wasm zur Laufzeit von cdn.mezon.ai. Für
eine selbstgehostete Plattform ist das nicht hinnehmbar: jeder Teilnehmer meldet bei jedem
Call-Start seine IP an einen Dritten, und die Verfügbarkeit des Calls hinge an fremder
Infrastruktur.
Entschärft: assetConfig.cdnUrl ist konfigurierbar. Der Prototyp liefert die Assets
bereits lokal aus — der CDN-freie Betrieb ist damit nachgewiesen, nicht nur angenommen.
Für eine Integration hieße das: die 23 MB gehören mit ausgeliefert (Image/Ingress), nicht
nachgeladen.
Ebenfalls korrigiert gegenüber der Spec
Die getUserMedia-Constraints, die im finalen Spec-Code fehlten, sind im Prototyp gesetzt:
noiseSuppression: false (sonst arbeiten Browser-Filter und Modell gegeneinander),
echoCancellation: true.
Weiterhin offen — und entscheidungsrelevant
- CPU-Last (Jitter mit/ohne Filter) noch nicht abgelesen.
- Telefon. Die eigentliche Härteprobe: 23 MB Download und DFN3-Inferenz auf einem Mittelklasse-Gerät. Fällt das durch, braucht es einen Pfad (RNNoise als leichte Variante, oder Filter auf Mobilgeräten aus).
- Fork-Wartung bleibt der ungemessene Posten — die Anpassung muss jeden Upstream-Rebase überleben.