3477cbe7ce
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>