Skip to content

Commit b87c972

Browse files
ten9876claude
andcommitted
Fix PC Audio silence caused by infinite RX restart loop (#1441)
Two fixes: 1. Buffer cap used int16 math (2 bytes/sample) but pipeline is float32 (4 bytes). Cap was half correct size, causing constant drops. 2. stopRxStream() emitted StoppedState before nulling m_audioSink, causing the stateChanged handler to queue another stop+start in an infinite loop. Co-Authored-By: AetherClaude & Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 6b16909 commit b87c972

1 file changed

Lines changed: 14 additions & 7 deletions

File tree

src/core/AudioEngine.cpp

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,10 +92,10 @@ AudioEngine::AudioEngine(QObject* parent)
9292
if (!m_audioSink || !m_audioDevice || !m_audioDevice->isOpen() || m_audioSink->state() == QAudio::StoppedState) return;
9393

9494
// Cap buffer at ~200ms of audio to bound latency.
95-
// At 24kHz stereo int16 = 96000 bytes/sec → 200ms = 19200 bytes.
96-
// At 48kHz stereo int16 = 192000 bytes/sec → 200ms = 38400 bytes.
95+
// At 24kHz stereo float32 = 192000 bytes/sec → 200ms = 38400 bytes.
96+
// At 48kHz stereo float32 = 384000 bytes/sec → 200ms = 76800 bytes.
9797
const int sampleRate = m_resampleTo48k ? 48000 : DEFAULT_SAMPLE_RATE;
98-
const qsizetype maxBufBytes = sampleRate * 2 * 2 / 5; // 200ms worth
98+
const qsizetype maxBufBytes = sampleRate * 2 * static_cast<qsizetype>(sizeof(float)) / 5; // 200ms worth
9999
if (m_rxBuffer.size() > maxBufBytes) {
100100
// Drop oldest samples to keep latency bounded
101101
m_rxBuffer.remove(0, m_rxBuffer.size() - maxBufBytes);
@@ -293,12 +293,19 @@ void AudioEngine::stopRxStream()
293293
m_rxBufferSampleRate.store(DEFAULT_SAMPLE_RATE);
294294

295295
if (m_audioSink) {
296-
// Guard: same stale-device-handle crash can occur on the RX side (#1059).
297-
if (m_audioSink->state() != QAudio::StoppedState)
298-
m_audioSink->stop();
299-
delete m_audioSink;
296+
// Null out m_audioSink BEFORE stopping so that the stateChanged
297+
// handler's "if (!m_audioSink) return" guard prevents a cascading
298+
// restart loop. Without this, stop() emits stateChanged(StoppedState)
299+
// synchronously while m_audioSink is still non-null, causing the
300+
// handler to queue another stopRx+startRx — which repeats
301+
// indefinitely and prevents audio from ever playing. (#1441)
302+
auto* sink = m_audioSink;
300303
m_audioSink = nullptr;
301304
m_audioDevice = nullptr;
305+
// Guard: same stale-device-handle crash can occur on the RX side (#1059).
306+
if (sink->state() != QAudio::StoppedState)
307+
sink->stop();
308+
delete sink;
302309
}
303310
emit rxStopped();
304311
}

0 commit comments

Comments
 (0)