Skip to content

fix(audio): replace QsoRecorder QTimer playback with QAudioSink pull mode#2487

Merged
ten9876 merged 1 commit into
aethersdr:mainfrom
wa2n-code:fix/qso-recorder-windows-playback-choppy
May 9, 2026
Merged

fix(audio): replace QsoRecorder QTimer playback with QAudioSink pull mode#2487
ten9876 merged 1 commit into
aethersdr:mainfrom
wa2n-code:fix/qso-recorder-windows-playback-choppy

Conversation

@wa2n-code

Copy link
Copy Markdown
Contributor

Fixes #2295

Root cause

QsoRecorder::startPlayback() used a 10 ms QTimer (CoarseTimer) to pace PCM chunk delivery into AudioEngine::feedDecodedSpeech. On Windows, the system clock tick is ~15.625 ms, so chunks arrive at irregular 16/16/32 ms intervals while the WASAPI shared-mode sink pulls on a tight 10 ms schedule — every missed refill inserts silence, producing choppy audio. On Linux, the 1 ms scheduler tick makes the same timer effectively precise, which is why it only reproduced on Windows.

Fix

Port the proven ClientPuduMonitor pattern:

  1. QsoRecorder::startPlayback() — reads the WAV payload into a QByteArray, opens a dedicated QAudioSink in pull mode with a 300 ms internal ring buffer (fmt.bytesForDuration(300'000)), and lets WASAPI/CoreAudio drain at its own cadence. No timer, no feedDecodedSpeech.
  2. QsoRecorder::preparePlaybackPcm() — tries 24 kHz passthrough first (zero-resample, common on Linux); falls back to 48 kHz with a one-shot r8brain resample per channel (common on macOS/Windows).
  3. QsoRecorder::onPlaybackSinkState() — stops cleanly when the sink reaches IdleState (buffer drained) or StoppedState.
  4. MainWindow.cpp — removes the now-unused playbackAudio → feedDecodedSpeech connection.

Files changed

  • src/core/QsoRecorder.h — swap m_playFile/m_playTimer for m_playSink/m_playBuffer/m_playPcm; remove playbackAudio signal; add private slot + helper
  • src/core/QsoRecorder.cpp — implement new playback path
  • src/gui/MainWindow.cpp — drop obsolete signal connection (2 lines)

Test plan

  • Record a client-side QSO clip on Windows, press play — audio should be clean (not choppy)
  • Verify playback still works on Linux (24 kHz passthrough path)
  • Verify stop button during playback works cleanly
  • Verify live RX mute is restored after playback ends

🤖 Generated with Claude Code

…mode (aethersdr#2295)

Windows' system clock tick (~15.625 ms) causes CoarseTimer-based chunk delivery
to starve the WASAPI shared-mode sink, inserting silence and producing choppy
playback on Windows. Linux's 1 ms tick makes the same 10 ms timer effectively
precise, explaining the platform asymmetry.

Port the proven ClientPuduMonitor pattern: read the WAV payload into a QBuffer,
open a dedicated QAudioSink in pull mode with a 300 ms internal ring buffer,
and let WASAPI/CoreAudio drain at its own cadence. Adds a 48 kHz r8brain
resample path (via Resampler) for sinks that don't support 24 kHz natively
(common on macOS/Windows). Drops the QTimer, QFile playback members, and the
playbackAudio → feedDecodedSpeech route in MainWindow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@aethersdr-agent aethersdr-agent Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @wa2n-code — this is a clean, faithful port of the existing ClientPuduMonitor playback pattern (same QAudioSink pull-mode setup, same 300 ms ring buffer rationale, same L/R-independent r8brain resample to preserve stereo image, same IdleState/StoppedState shutdown). The root-cause analysis (Windows ~15.625 ms timer tick vs. WASAPI 10 ms pull cadence) matches what we saw with PUDU.

Scope check is good:

  • All 3 files are tightly scoped to the playback path.
  • The removed playbackAudio signal has no other consumers.
  • QsoRecorder lives on the main thread (parented to MainWindow), so direct QAudioSink/QBuffer ownership is fine.
  • m_playBuffer.setBuffer(&m_playPcm) is safe because m_playPcm is a stable member address and the buffer is closed before each new playback.

A couple of minor observations, none blocking:

  1. startPlayback() early-returns are silent. If defaultAudioOutput() is null, neither 24 kHz nor 48 kHz is supported, or preparePlaybackPcm() fails, the user clicks Play and nothing happens with no feedback. Existing ClientPuduMonitor does the same, so this is consistent — but since QsoRecorder already has a recordingError signal pattern, you could optionally add a playbackError(QString) for these paths. Up to you / a follow-up.

  2. Potential synchronous stateChanged during start(). If QAudioSink::start() ever emits IdleState synchronously before the next line m_playing = true; runs, onPlaybackSinkStatestopPlayback() would early-return on !m_playing, leaving the sink running with m_playing=false. PUDU has the exact same ordering and ships fine, so I'd expect the same here, but if you want belt-and-suspenders you could flip the order: set m_playing = true before m_playSink->start(&m_playBuffer).

LGTM otherwise — happy to see this lifted out of the timer-paced path.

@ten9876 ten9876 merged commit 182fb21 into aethersdr:main May 9, 2026
5 checks passed
@ten9876

ten9876 commented May 9, 2026

Copy link
Copy Markdown
Collaborator

Claude here — really clean cross-platform diagnostic, Wayne. The 'Windows 15.625 ms clock tick vs Linux 1 ms' framing is the kind of insight that's only obvious when you actually test on both platforms — exactly why this got past the original commit. Reusing the ClientPuduMonitor pull-mode pattern is the right call (proven shape, no invention) and the per-channel resample mirrors the lesson from your #2459 fix earlier this session. Format-negotiation order is also right: 24 kHz first means Linux's zero-resample path is preserved, only Windows/macOS pay for the resample. Merged.

73, Jeremy KK7GWY & Claude (AI dev partner)

@wa2n-code wa2n-code deleted the fix/qso-recorder-windows-playback-choppy branch June 28, 2026 04:06
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.

Client Side recording playback choppy on Windows 11 but not Linux

2 participants