Skip to content

Address Windows output endpoints by ID, not by name - #213

Merged
Turbootzz merged 2 commits into
mainfrom
212-windows-audio-endpoint-ids
Sep 12, 2026
Merged

Turbootzz merged 2 commits into
mainfrom
212-windows-audio-endpoint-ids

Conversation

@Turbootzz

Copy link
Copy Markdown
Member

Closes #212. Both bugs are Windows-only, both silent, both from the output picker in #210.

The wrong endpoint

list_audio_outputs stored PKEY_Device_FriendlyName as the value the capture backend resolves. Identical hardware gives two endpoints the same friendly name, so picking the second of a pair resolved to the first and the loopback recorded an output nobody was listening to: clips with no system audio and nothing in the log to explain it. The picker now stores GetId() and shows the friendly name, which is what endpoint() already tried first.

The doubled mix

endpoint() fell back to the console default whenever a configured render endpoint matched nothing, deliberately: recording the current output beats recording silence when the user's pick disappears. That fallback also covered the internal communications-endpoint capture. If the comms endpoint went inactive between default_render_endpoints() reading it and the capture thread resolving its opaque ID (headset unplugged, comms default changed), the extra capture opened a second loopback on the console default — which the system capture is already recording, since comms is only spawned when the user picked no output. Both fed AudioMixer::add, so clips carried system audio at roughly double amplitude, clamped on drain into distortion, behind a single info log.

The fallback is right for one caller and wrong for the other, so the call now says which it means:

pub enum AudioDevice {
    Default,           // the platform default
    Preferred(String), // the user's pick; substituting the default beats silence
    Exact(String),     // this endpoint or nothing
}

capture_audio takes it in place of Option<&str> on all three backends. Windows falls back only for Preferred on the render flow; the comms capture passes Exact, so when its endpoint is gone the capture simply does not start — which main already tolerates, since it is spawned as an optional extra. Linux and macOS treat Preferred and Exact alike (PipeWire's session manager and SCK decide substitution themselves), documented at the enum.

Upgraded configs

A config written by an earlier build holds a friendly name. Capture still resolves it, through the same name match as before, so recording is unaffected. In the picker that value would have produced two identical rows (the real endpoint plus the "offline device" row that keeps an unlisted value visible), so the picker now matches a listed endpoint's label as well as its id; the stored value becomes an ID the next time anything is picked. Rewriting it on open is deliberately not done — the settings window only writes on Save, so that would show unsaved edits nobody made.

Verified

  • cargo fmt --all --check, cargo clippy --workspace --all-targets -- -D warnings, cargo build --workspace --all-targets, cargo test --workspace (23 test binaries, no failures) — plus three new tests covering the selector and which variants may be substituted.
  • cargo check --target x86_64-pc-windows-msvc -p rewynd-capture -p rewynd-config for the two crates that hold the actual fixes. The app crate cannot cross-compile here (C build scripts want the MSVC toolchain), so its Windows block is on CI's windows job.

Two silent bugs in the audio-output picker, both on Windows.

Identical hardware gives two endpoints the same friendly name, so storing that
name meant picking the second of a pair resolved to the first: the loopback
recorded the wrong output and clips came out with no system audio. The picker now
stores the endpoint ID and shows the friendly name. Existing configs keep working
through the friendly-name match the resolver already tries after the ID.

The resolver also fell back to the default output whenever a configured render
endpoint matched nothing, which is right for the user's pick and wrong for the
communications loopback: if that endpoint went inactive between being read and
being resolved, the extra capture landed on the console default, which the system
capture already records, and the mixer summed the same audio twice. Captures now
say which they mean - AudioDevice::Preferred may be substituted, Exact may not -
so the comms capture simply does not start instead of doubling the mix.
A Windows config written before this change holds a friendly name where the picker
now expects an endpoint ID, which gave the saved device two identical rows: the
real endpoint plus the "offline device" row that keeps an unlisted value visible.
Matching a listed endpoint's label as well as its id collapses that back to one
row, and the value becomes an ID as soon as anything is picked. Migrating it
outright is deliberately left alone: the settings window only writes on Save, so
rewriting it on open would show unsaved edits nobody made.
@Turbootzz

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 9 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: e1c62474-5e03-45b2-bd4f-407e6ce8b1aa

📥 Commits

Reviewing files that changed from the base of the PR and between 1e97388 and 5589586.

📒 Files selected for processing (8)
  • crates/app/src/main.rs
  • crates/capture/examples/audio_probe.rs
  • crates/capture/src/lib.rs
  • crates/capture/src/linux/audio.rs
  • crates/capture/src/macos/audio.rs
  • crates/capture/src/windows/wasapi_audio.rs
  • crates/config/src/devices.rs
  • crates/settings/src/main.rs

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai

coderabbitai Bot commented Sep 12, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@Turbootzz Turbootzz self-assigned this Sep 12, 2026
@Turbootzz
Turbootzz merged commit d5166fc into main Sep 12, 2026
6 checks passed
@Turbootzz
Turbootzz deleted the 212-windows-audio-endpoint-ids branch September 12, 2026 20:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Windows audio: address endpoints by ID, and never fall back for the comms loopback

1 participant