Files
management/docs/adr/0018-ki-geraeuschunterdrueckung-clientseitig-opt-in.md
Thore CimbalandClaude Opus 4.8 914eb59575 docs(adr): ADR-0018 — client-side noise suppression, opt-in and self-hosted
sorb's decision after the prototype: integrate DeepFilterNet3 as a LiveKit track
processor, off by default, assets fetched only when the user enables it, checkbox
plus slider, 35 percent default.

Opt-in is what makes the 23.3 MB affordable — only those who benefit pay for it.
Three of the source specification's assumptions did not survive measurement and are
recorded as rejected alternatives: the dry/wet mixer (the model limits attenuation
natively, and mixing raw signal back would return the keystrokes), the Rust/wasm
build (a maintained package makes it unnecessary), and loading assets from the
vendor CDN (every participant's IP to a third party at call start).

Mobile stays untested by choice; since the filter is opt-in it simply stays off on
weak devices, so that is a follow-up rather than a blocker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00

5.3 KiB
Raw Permalink Blame History

type, id, status, date, supersedes, superseded_by, related
type id status date supersedes superseded_by related
adr 0018 accepted 2026-08-15 null null
docs/issues/0054-ki-geraeuschunterdrueckung-element-call.md

0018 — KI-Geräuschunterdrückung in threadnet-call: client-seitig, opt-in, selbst ausgeliefert

Status: akzeptiert · Datum: 2026-08-15 · Entscheider: sorb

Kontext

Der WebRTC-Standardfilter (noiseSuppression) schätzt ein laufendes Rauschprofil und ist damit auf stationäre Störungen ausgelegt. Transiente Geräusche — Tastaturanschläge — erkennt er nicht als Störung; sie werden mitübertragen. Betroffen sind ausdrücklich auch leise Chiclet-Tastaturen, nicht nur mechanische. Push-to-Talk als Ausweg wurde verworfen („inakzeptabel", sorb).

threadnet-call:docs/axion1337-fork.md §5 hatte ML-Rauschunterdrückung bereits einmal verworfen — allerdings server-seitig (LiveKit Agents), weil es dort keinen unterstützten Weg gibt, bereinigtes Audio an andere Teilnehmer weiterzureichen. Client-seitig greift dieser Einwand nicht; diese ADR widerspricht der damaligen Entscheidung also nicht, sondern setzt sie fort.

Eine externe Architekturspezifikation empfahl DeepFilterNet3 via WebAssembly. Statt sie zu übernehmen, wurde ein Wegwerf-Prototyp gebaut und gemessen (#0054). Das hat drei ihrer Kernannahmen korrigiert und die Entscheidungsgrundlage von Schätzung auf Messung gestellt.

Entscheidung

DeepFilterNet3 wird client-seitig integriert — opt-in, nachgeladen, selbst ausgeliefert.

  1. Modell: DeepFilterNet3 über deepfilternet3-noise-filter (Apache-2.0 ODER MIT) als LiveKit-TrackProcessor. Nicht RNNoise: es ist zwar zehnmal kleiner, aber bei Transienten deutlich schwächer — genau dem Fall, um den es hier geht.
  2. Opt-in mit Nachladen. Standard AUS. Die Assets (23,3 MB) werden erst beim Einschalten geladen. Damit zahlt nur, wer profitiert — das Größenargument entfällt für alle anderen.
  3. Bedienung: Checkbox zum Aktivieren plus Regler für die Stärke.
  4. Standardwert 35 %, nicht 100 %. Gemessen reicht gut ein Drittel für „Tastatur weg und Stimme natürlich"; weniger Dämpfung heißt weniger Artefaktrisiko.
  5. Regelung über den Modellparameter (setSuppressionLevel / atten_lim), nicht über einen Dry/Wet-Mix.
  6. Assets werden selbst ausgeliefert. Der Default-Pfad des Pakets lädt sie von cdn.mezon.ai; assetConfig.cdnUrl wird auf das eigene Deployment gezeigt.
  7. getUserMedia: noiseSuppression: false (sonst arbeiten Browser-Filter und Modell gegeneinander), echoCancellation: true.

Gemessene Grundlage (Prototyp 2026-08-15)

Wirkung Tastatur weg, Stimme natürlich — bei 35 %
Download je Client 23,27 MB (15,66 MB wasm + 7,61 MB Modell)
Vergleich RNNoise 2,04,6 MB, also ~ein Zehntel
Paketpflege 20 Versionen, 4 Maintainer, ~20k Downloads/Monat
CDN-freier Betrieb im Prototyp nachgewiesen

Konsequenzen

  • Kein Rust/wasm-Build. Die Spezifikation hielt ihn für nötig; das fertige Paket macht ihn entbehrlich. Die Fork-Anpassung bleibt dadurch klein: Abhängigkeit, TrackProcessor, Bedienelement, Assets in der Auslieferung.
  • Kein Dry/Wet-Mixer, kein Delay-Node. Da über den Modellparameter geregelt wird, gibt es keinen zweiten Signalpfad — und damit weder Phasenauslöschung noch Latenzkompensation.
  • 23,3 MB gehören ins Deployment. Sie müssen mit ausgeliefert werden (Image/Ingress) und wachsen bei einem Modell-Update mit.
  • Dauerhafte Fork-Anpassung. Sie muss jeden Upstream-Rebase überleben und gehört in axion1337-fork.md samt Portier-Hinweis.
  • ⚠️ Mobil ist ungeprüft. Der Telefontest wurde bewusst ausgesetzt. Weil der Filter opt-in ist, ist das vertretbar: Auf schwachen Geräten bleibt er schlicht aus. Zeigt sich später, dass er dort unbrauchbar ist, ist das ein Folge-Issue, kein Widerruf dieser Entscheidung.
  • Neue Lieferkette. Ein npm-Paket eines Drittanbieters liefert Code, der WebAssembly lädt. Die Assets kontrollieren wir (selbst gehostet); der 23-KB-Wrapper bleibt Fremdcode. Vendoring wäre möglich und wurde bewusst nicht gewählt — dann müssten wir Updates selbst nachziehen.

Verworfene Alternativen

  • Push-to-Talk / bewusstes Stummschalten dokumentieren: billigste Lösung, aber ein Rückschritt gegenüber dem, was Konferenzsysteme heute leisten. Ausdrücklich verworfen.
  • RNNoise statt DFN3: ein Zehntel der Größe, aber konzeptionell schwach bei genau den transienten Geräuschen, die das Problem sind.
  • Server-seitige ML-Filterung: bereits in axion1337-fork.md §5 verworfen — LiveKit bietet keinen unterstützten Weg, bereinigtes Audio an andere Teilnehmer weiterzugeben.
  • Standardmäßig AN: beste Wirkung ohne Zutun, aber jeder Client lädt 23 MB — auch auf Telefonen, die ungetestet sind.
  • Dry/Wet-Mix als Regler (Vorschlag der Spezifikation): mischt ungefiltertes Signal zurück, inklusive der Tastaturanschläge, und braucht eine Latenzkompensation, die im Spec-Entwurf fehlte. Der native Modellparameter leistet dasselbe ohne diese Nachteile.
  • Assets vom Anbieter-CDN laden: würde bei jedem Call-Start die IP jedes Teilnehmers an einen Dritten melden und die Verfügbarkeit an fremde Infrastruktur hängen.