HOLD FOR TESTING - fix(audio): reduce Windows USB audio latency for RX and CW sidetone (#3193)#3194
HOLD FOR TESTING - fix(audio): reduce Windows USB audio latency for RX and CW sidetone (#3193)#3194M7HNF-Ian wants to merge 1 commit into
Conversation
…ethersdr#3193) Two root causes contributed to audible latency on class-compliant USB interfaces (Scarlett Solo Gen4 and similar) on Windows: 1. AudioEngine::startRxStream() created the QAudioSink without calling setBufferSize(), so WASAPI shared mode applied its default ring buffer (100–300 ms for USB devices), stacking on top of m_rxBufferCapMs and producing 500 ms+ speaker latency. Fix: cap the WASAPI ring to 100 ms (≈38 KB at 48 kHz Float32 stereo) before start(), mirroring the explicit 50 ms buffer already set in CwSidetoneQAudioSink. 2. CwSidetonePortAudioSink picked devices by first-match, which on Windows resolves to an MME device (50–150 ms OS buffering) rather than WASAPI (≈10 ms). This caused CW timing jitter on fast keying. Fix: defaultPortAudioOutputDevice() now prefers the WASAPI host API on Windows (mirroring the Linux JACK preference already in place), and findPortAudioOutputDevice() resolves ambiguous multi-API partial matches by selecting the WASAPI candidate over MME/DirectSound alternatives. Fixes aethersdr#3193
|
Thanks Ian, since this touches the audio stack for all users on Windows, I want to give this some soak time for testing. |
|
@AetherClaude please provide a defensive review of this fix with full Cw and audio sink history on Windows. include a table of previous issues and PRs. because of the audio stack impact I'm especially concerned about fix 1 and it's impact on system and USB devices. |
Defensive review — Windows audio sink history & risk assessmentPat, here's a defensive look at PR #3194 against the full Win audio/CW history. TL;DR: Fix #2 (PortAudio sidetone) is low-risk and well-scoped. Fix #1 (RX WASAPI ring cap) is correct in intent but has device-specific failure modes worth shaking out before merge. Historical context — Windows RX / CW sink issues & PRs
Fix #1 (RX
|
|
Ian, please roll up CW fix 2 in a standalone PR and I will approve. I am not going to apply fix 1 for now because it impacts buffer sizes all for interface types, not just USB. On these sensitive audio stack changes, it's important to roll separate PRs as changes to the audio stack require extensive testing. Thanks for the fixes! |
…3241) Standalone split of Fix #2 from #3194, as requested by @jensenpat. ## Problem `CwSidetonePortAudioSink` picks audio output devices via PortAudio, but on Windows: 1. **`defaultPortAudioOutputDevice()`** falls straight through to `Pa_GetDefaultOutputDevice()`, which returns an MME device (the first host API PortAudio enumerates). MME carries 50–150 ms OS-level buffering, producing audible CW timing jitter on fast keying. 2. **`findPortAudioOutputDevice()`** matches devices by name substring. On Windows, the same physical device is enumerated three times under MME, DirectSound, and WASAPI. The old code treated this as an ambiguous match and returned `paNoDevice` — causing the sidetone to fall back to `CwSidetoneQAudioSink` and lose low-latency timing. When it did resolve, it picked whichever host API appeared first (MME). ## Fix **`defaultPortAudioOutputDevice()`** — add a WASAPI preference pass before the `Pa_GetDefaultOutputDevice()` fallback, mirroring the existing Linux JACK preference exactly. WASAPI shared mode runs at ~10 ms, vs 50–150 ms for MME. **`findPortAudioOutputDevice()`** — collect all partial-match candidates, then resolve to the WASAPI entry when exactly one exists. Logs a clear `qCInfo` message showing which host API won. Falls back to the existing "matched multiple" warning only if multiple WASAPI entries are somehow present (pathological case). ## Scope `CwSidetonePortAudioSink.cpp` only. No changes to `AudioEngine.cpp`, no RX path changes, no buffer size changes. ## Related - Partially addresses #3193 (CW sidetone latency component) - May also help #2528 (TX→RX delay when stopping CW) and the latency component of #2694 - Sibling of the held #3194 Fix #1 (RX WASAPI ring cap — separate review track)
Problem
On Windows with class-compliant USB audio interfaces (Scarlett Solo Gen4, Focusrite, etc.), two independent code paths contribute to audible latency:
1. RX audio —
AudioEngine::startRxStream()(AudioEngine.cpp)QAudioSinkis constructed andstart()called with no priorsetBufferSize(). WASAPI shared mode then applies the device's default ring buffer, which for USB class-compliant interfaces is typically 100–300 ms. This stacks on top ofm_rxBufferCapMs(200 ms), producing 500 ms+ speaker latency.2. CW sidetone —
CwSidetonePortAudioSink(CwSidetonePortAudioSink.cpp)Pa_GetDefaultOutputDevice()on Windows returns an MME device (the first enumerated host API), which carries 50–150 ms OS-level buffering. When a user selects a specific output and the name is matched by substring against PortAudio's device list, the same physical device appears under MME, DirectSound, and WASAPI — the old code treated this as an ambiguous match and fell back topaNoDevice. The result is CW timing jitter on fast keying.Fix
AudioEngine.cpp— addsetBufferSize(100 ms)beforestart()for both the 48 kHz path and the 24 kHz fallback path inside#ifdef Q_OS_WIN, mirroring the explicit 50 ms buffer already set inCwSidetoneQAudioSink.CwSidetonePortAudioSink.cpp— two changes:defaultPortAudioOutputDevice(): prefer the WASAPI host API on Windows (mirrors the existing Linux JACK preference).findPortAudioOutputDevice(): collect all partial-match candidates; when multiple host APIs expose the same device, resolve to the WASAPI candidate instead of returningpaNoDevicewith an "ambiguous" warning. Falls back to the existing warning if multiple WASAPI matches exist.Testing
Tested locally on macOS (no Windows-path regression —
#ifdef Q_OS_WINguards both changes). Windows testing with a Scarlett Solo Gen4 is needed to confirm latency improvement; the reporter in #3193 should be able to verify.Fixes #3193