Anyone in a Discord call on speakers rather than headphones sends their own
output back into the channel, and nothing anywhere in the path breaks the loop.
The cause is an asymmetry between the two call stacks. A Matrix call is made in
the window, so its microphone comes from navigator.mediaDevices.getUserMedia
(src/renderer/src/lib/webrtc.ts:221) and arrives with Chromium's AEC3, noise
suppression and gain control already applied - they are on by default and cost
nothing to have. A Discord call is made in the daemon, so its microphone is a
raw cpal capture (nobilis/src/audio.rs:415, held by backend/discord/voice.rs:92), and a raw capture has none of that. There is no
APM in the daemon at all: no AEC, no noise suppression, no AGC.
So the same person on the same hardware is clean on Matrix and echoes on
Discord, which is the confusing way round - it reads as a Discord problem
rather than a microphone one.
Found while testing a Go Live stream between two accounts on one machine, where
it is at its loudest, but the self-test only makes it obvious. It is there for
every real call too.
The fix is an APM in the daemon between the capture and the encoder. The webrtc-audio-processing crate wraps the same AEC3 Chromium uses, and it needs
the playback stream as its reference signal - which voice.rs already holds
beside the capture, in playbacks next to captures. So the two signals it
has to line up are already in the same struct.
This lands under every Discord call, not just Go Live, so it is worth having
before #231 and before the viewing half of #14.
Anyone in a Discord call on speakers rather than headphones sends their own
output back into the channel, and nothing anywhere in the path breaks the loop.
The cause is an asymmetry between the two call stacks. A Matrix call is made in
the window, so its microphone comes from `navigator.mediaDevices.getUserMedia`
(`src/renderer/src/lib/webrtc.ts:221`) and arrives with Chromium's AEC3, noise
suppression and gain control already applied - they are on by default and cost
nothing to have. A Discord call is made in the daemon, so its microphone is a
raw cpal capture (`nobilis/src/audio.rs:415`, held by
`backend/discord/voice.rs:92`), and a raw capture has none of that. There is no
APM in the daemon at all: no AEC, no noise suppression, no AGC.
So the same person on the same hardware is clean on Matrix and echoes on
Discord, which is the confusing way round - it reads as a Discord problem
rather than a microphone one.
Found while testing a Go Live stream between two accounts on one machine, where
it is at its loudest, but the self-test only makes it obvious. It is there for
every real call too.
The fix is an APM in the daemon between the capture and the encoder. The
`webrtc-audio-processing` crate wraps the same AEC3 Chromium uses, and it needs
the playback stream as its reference signal - which `voice.rs` already holds
beside the capture, in `playbacks` next to `captures`. So the two signals it
has to line up are already in the same struct.
This lands under every Discord call, not just Go Live, so it is worth having
before #231 and before the viewing half of #14.
Salastil
added the bug label 2026-09-23 15:16:24 -04:00
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Anyone in a Discord call on speakers rather than headphones sends their own
output back into the channel, and nothing anywhere in the path breaks the loop.
The cause is an asymmetry between the two call stacks. A Matrix call is made in
the window, so its microphone comes from
navigator.mediaDevices.getUserMedia(
src/renderer/src/lib/webrtc.ts:221) and arrives with Chromium's AEC3, noisesuppression and gain control already applied - they are on by default and cost
nothing to have. A Discord call is made in the daemon, so its microphone is a
raw cpal capture (
nobilis/src/audio.rs:415, held bybackend/discord/voice.rs:92), and a raw capture has none of that. There is noAPM in the daemon at all: no AEC, no noise suppression, no AGC.
So the same person on the same hardware is clean on Matrix and echoes on
Discord, which is the confusing way round - it reads as a Discord problem
rather than a microphone one.
Found while testing a Go Live stream between two accounts on one machine, where
it is at its loudest, but the self-test only makes it obvious. It is there for
every real call too.
The fix is an APM in the daemon between the capture and the encoder. The
webrtc-audio-processingcrate wraps the same AEC3 Chromium uses, and it needsthe playback stream as its reference signal - which
voice.rsalready holdsbeside the capture, in
playbacksnext tocaptures. So the two signals ithas to line up are already in the same struct.
This lands under every Discord call, not just Go Live, so it is worth having
before #231 and before the viewing half of #14.
Finished
https://git.salastil.com/Salastil/nobilis/commit/72ef1b5
https://git.salastil.com/Salastil/moho/commit/ccd9936