fix(audio/windows): stop the client mic echoing back through the loopback
The Windows virtual mic fakes a capture endpoint by writing the client's uplinked PCM into a virtual device's *render* endpoint, while the desktop-audio plane loopback-captures the *default render* endpoint — with no mutual exclusion between the two. WASAPI loopback captures the mixed output of an endpoint (everything any app renders to it, including our mic writes), so when both resolve to the same device — VB-CABLE used for both, or the auto-installed Steam Streaming Microphone being the default render on a headless box — the injected mic is captured straight back into the host->client audio stream: an infinite echo. find_device() now resolves the loopback's endpoint id (default render) and skips any candidate matching it, scanning on to the next non-loopback match, so the mic can never land on the device the loopback reads. The auto-install path now provisions the full Steam pair (Streaming Microphone + Streaming Speakers) so a bare host gets two distinct devices instead of one shared one. Errors distinguish "no device" from "only candidate is the loopback device". Linux was already immune (its mic is a dedicated Audio/Source node, structurally separate from the monitored sink). Windows-only (#[cfg(windows)]); rustfmt-clean, compile-checked in windows-host CI, needs on-glass validation on the RTX box. Does not force the system default playback onto Steam Streaming Speakers (IPolicyConfig) — not required to break the echo. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -106,7 +106,10 @@ fn capture_thread(
|
||||
}
|
||||
let res = (|| -> Result<()> {
|
||||
// Loopback = capture the RENDER endpoint: get the default render device, but open a CAPTURE
|
||||
// client with loopback=true over it.
|
||||
// client with loopback=true over it. NOTE: the virtual mic (`super::wasapi_mic`) is guarded
|
||||
// to NEVER target this same endpoint — otherwise the client's injected mic would be captured
|
||||
// here and streamed back to the client (infinite echo). Keep that guard in sync if this
|
||||
// device selection ever changes.
|
||||
let device = DeviceEnumerator::new()
|
||||
.context("DeviceEnumerator")?
|
||||
.get_default_device(&Direction::Render)
|
||||
|
||||
Reference in New Issue
Block a user