Prefer 48kHz RX audio sink on Windows to avoid WASAPI resampler artifacts (#2120)#2123
Conversation
…acts (#2120) WASAPI shared mode accepts 24kHz but delegates rate conversion internally, introducing low-level artifacts that become audible when radio-side NR (especially RNN) aggressively removes the noise floor. Swap the sink preference to try 48kHz first, routing through r8brain for clean 24k→48k conversion — matching the macOS TX-side approach for the same class of issue. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Clean, well-scoped change. The logic correctly inverts the Windows RX sink preference from 24kHz-first to 48kHz-first, with 24kHz as fallback — mirroring the macOS path that already prefers 48kHz for the same class of resampler artifact issue.
A few observations:
-
Correctness: The
fmtstarts atDEFAULT_SAMPLE_RATE(24000) frommakeFormat(). The PR sets it to 48000 before the first sink attempt, and the fallback correctly resets toDEFAULT_SAMPLE_RATEwithm_resampleTo48k = false. The flag and format stay consistent in both success paths. Looks good. -
Fallback log messages: Updated to match the new order ("48kHz failed, trying 24kHz" / "24kHz also failed"). Correct.
-
Scope: Single file, only the Windows
#ifdef Q_OS_WINblock is touched. No files outside the stated scope. -
No new resource or null-pointer risks: The sink creation/deletion pattern is unchanged — just the order is swapped. The existing
delete m_audioSink+ re-create pattern on fallback is preserved.
One minor note for the maintainer: the comment block was updated from describing WASAPI's transparent sample rate conversion to explaining why we now avoid relying on it. The new comment references the macOS TX-side fix and issue #2120, which is helpful context. Worth confirming the referenced macOS fix is the one in the non-Windows configureOutputFormat lambda (lines ~528-534) that already prefers 48kHz — it appears to be.
No bugs or convention issues found. Thanks for the fix, @aethersdr-agent — this is a clean, focused change. 👍
…3397) ## What First, **pure-addition** step of the consolidated audio sink factory (#3306): a single shared, OS-parameterized format/rate **negotiation policy** that every audio sink and source will migrate onto, replacing the ~9 divergent per-sink fallback ladders and per-OS `#ifdef` branches that are the root of a recurring class of platform audio bugs. Zero behaviour change — this is the maintainer-endorsed *"land the helper first, migrate sinks incrementally"* approach (#3306, and the #3194 thread's "sensitive audio-stack changes must be separate PRs with soak time"). ## Why this shape The historical bugs escaped CI because the per-OS rate ladders were compiled behind `Q_OS_*`, so a Linux runner could only ever exercise the Linux path. The new policy is a **pure function over an injected `DeviceCaps` snapshot with `TargetOs` as a parameter, not an `#ifdef`** — so one headless, hardware-free test binary exercises the Windows, macOS *and* Linux ladders on any runner. ## Contents - **`src/core/AudioFormatNegotiator.{h,cpp}`** — dependency-free policy (links only `Qt6::Core`). One ladder owns: - Windows force-48k + r8brain (#2120/#2123), macOS 48k/A2DP (#1705), Linux native-24k — divergences preserved as **data**, not `#ifdef`. - The **universal 44.1 kHz rung** that RX/Quindar/QSO are missing today (#3385). - `Int16`↔`Float32` + `preferredFormat()` catch-all (#2669 / #1090 / #3231). - macOS mic preferred-rate-first (#2930) and Bluetooth-HFP native rate (#2615); Windows probe-at-open (#2929). - The two stereo resampler strategies — **PreservePan** (RX/QSO) vs **MonoCollapse** (TCI-TX/RADE) — kept deliberately distinct (#2403/#2459). - **`tests/audio_format_negotiation_test.cpp`** — table-driven golden matrix, registered under CTest. **25/25 pass**, including the 44.1k-only-device regression guard. - **`docs/audio-sink-factory.md`** — the full three-layer design (pure policy → live Qt wrapper → device-ownership router), the device-following **"uncoupling"** fix (CW / RADE / Aetherial-Audio "Pudu" recorder following the selected output), the regression-guard invariants, and the one-sink-per-PR migration plan. ## Test ``` $ ./build/audio_format_negotiation_test ... 25/25 checks passed ``` ## Follow-ups (per the migration plan in the doc) Live Qt wrapper → migrate RX speaker → migrate PC mic → `AudioOutputRouter` (closes the uncoupling class) → TCI-TX client-rate → PipeWire DAX shared resampler. Each a separate, soakable PR. Refs #3306 💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat Co-authored-by: Codex <noreply@openai.com>
…#3306) (aethersdr#3397) ## What First, **pure-addition** step of the consolidated audio sink factory (aethersdr#3306): a single shared, OS-parameterized format/rate **negotiation policy** that every audio sink and source will migrate onto, replacing the ~9 divergent per-sink fallback ladders and per-OS `#ifdef` branches that are the root of a recurring class of platform audio bugs. Zero behaviour change — this is the maintainer-endorsed *"land the helper first, migrate sinks incrementally"* approach (aethersdr#3306, and the aethersdr#3194 thread's "sensitive audio-stack changes must be separate PRs with soak time"). ## Why this shape The historical bugs escaped CI because the per-OS rate ladders were compiled behind `Q_OS_*`, so a Linux runner could only ever exercise the Linux path. The new policy is a **pure function over an injected `DeviceCaps` snapshot with `TargetOs` as a parameter, not an `#ifdef`** — so one headless, hardware-free test binary exercises the Windows, macOS *and* Linux ladders on any runner. ## Contents - **`src/core/AudioFormatNegotiator.{h,cpp}`** — dependency-free policy (links only `Qt6::Core`). One ladder owns: - Windows force-48k + r8brain (aethersdr#2120/aethersdr#2123), macOS 48k/A2DP (aethersdr#1705), Linux native-24k — divergences preserved as **data**, not `#ifdef`. - The **universal 44.1 kHz rung** that RX/Quindar/QSO are missing today (aethersdr#3385). - `Int16`↔`Float32` + `preferredFormat()` catch-all (aethersdr#2669 / aethersdr#1090 / aethersdr#3231). - macOS mic preferred-rate-first (aethersdr#2930) and Bluetooth-HFP native rate (aethersdr#2615); Windows probe-at-open (aethersdr#2929). - The two stereo resampler strategies — **PreservePan** (RX/QSO) vs **MonoCollapse** (TCI-TX/RADE) — kept deliberately distinct (aethersdr#2403/aethersdr#2459). - **`tests/audio_format_negotiation_test.cpp`** — table-driven golden matrix, registered under CTest. **25/25 pass**, including the 44.1k-only-device regression guard. - **`docs/audio-sink-factory.md`** — the full three-layer design (pure policy → live Qt wrapper → device-ownership router), the device-following **"uncoupling"** fix (CW / RADE / Aetherial-Audio "Pudu" recorder following the selected output), the regression-guard invariants, and the one-sink-per-PR migration plan. ## Test ``` $ ./build/audio_format_negotiation_test ... 25/25 checks passed ``` ## Follow-ups (per the migration plan in the doc) Live Qt wrapper → migrate RX speaker → migrate PC mic → `AudioOutputRouter` (closes the uncoupling class) → TCI-TX client-rate → PipeWire DAX shared resampler. Each a separate, soakable PR. Refs aethersdr#3306 💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat Co-authored-by: Codex <noreply@openai.com>
Summary
Fixes #2120
What was changed
Prefer 48kHz RX audio sink on Windows to avoid WASAPI resampler artifacts (#2120)
Files modified
src/core/AudioEngine.cppGenerated by AetherClaude (automated agent for AetherSDR)