Skip to content

Commit 65c6472

Browse files
SG-40625 - Play Forward/Pause button stops responding when playing media files (Linux) (#994)
SG-40625 - Play Forward/Pause button stops responding when playing media files (Linux) ### Linked issues n/a ### Summarize your change. ### Describe the reason for the change. There was a stability problem where calling `stop()` or `reset()` on the `QAudioOutput` object would cause audio playback to break after multiple play/stop cycles. To work around this, suspend() was used instead, which solved the instability. However, this introduced a second problem: `suspend()` does not clear the audio device's internal buffer. This resulted in an audio bleed issue, where sound from the previously played media could be heard for a fraction of a second when starting a new one. ### Describe what you have tested and on which operating system. This change introduces a new flushing mechanism that resolves the audio bleed on Linux while avoiding the unstable `stop() `and `reset()` calls. When playback is stopped, instead of immediately suspending the audio device, a new flushing process is initiated. The audio device is kept active momentarily while it is fed with silent audio data, completely overwriting the contents of its internal buffer. Once the buffer is flushed, the device is safely suspended. This ensures that the audio buffer is clear before the next playback begins, eliminating audio bleed. Because this approach continues to use `suspend()` to halt the device, it maintains the stability of the audio system. **This logic is confined to the Linux platform and does not affect the behavior on MacOS or Windows.** ### Add a list of changes, and note any that might need special attention during the review. ### If possible, provide screenshots. --------- Signed-off-by: Cédrik Fuoco <[email protected]>
1 parent a7b9e17 commit 65c6472

File tree

2 files changed

+71
-2
lines changed

2 files changed

+71
-2
lines changed

src/lib/audio/QTAudioRenderer/QTAudioRenderer.cpp

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,8 @@ namespace IPCore
659659
// with the audio thread.
660660
//----------------------------------------------------------------------
661661
QTAudioIODevice::QTAudioIODevice(QTAudioThread& audiothread)
662-
: m_thread(audiothread){QTAUDIO_DEBUG("QTAudioIODevice:")}
662+
: m_thread(audiothread)
663+
, m_silence(false){QTAUDIO_DEBUG("QTAudioIODevice:")}
663664

664665
QTAudioIODevice::~QTAudioIODevice()
665666
{
@@ -686,6 +687,23 @@ namespace IPCore
686687
qint64 QTAudioIODevice::readData(char* data, qint64 maxLenInBytes)
687688
{
688689
// QTAUDIO_DEBUG("QTAudioIODevice::readData")
690+
if (m_thread.audioOutput()->isFlushing())
691+
{
692+
memset(data, 0, maxLenInBytes);
693+
m_thread.audioOutput()->accumulateFlushedBytes(maxLenInBytes);
694+
if (m_thread.audioOutput()->flushedEnough())
695+
{
696+
m_thread.audioOutput()->suspendAudio();
697+
m_thread.audioOutput()->doneFlushing();
698+
}
699+
return maxLenInBytes;
700+
}
701+
702+
if (m_silence)
703+
{
704+
memset(data, 0, maxLenInBytes);
705+
return maxLenInBytes;
706+
}
689707
int bytesWritten = m_thread.qIODeviceCallback(data, maxLenInBytes);
690708
return bytesWritten;
691709
}
@@ -712,7 +730,9 @@ namespace IPCore
712730
, m_device(audioDevice)
713731
, m_format(audioFormat)
714732
, m_ioDevice(ioDevice)
715-
, m_thread(audioThread){QTAUDIO_DEBUG("QTAudioOutput")}
733+
, m_thread(audioThread)
734+
, m_isFlushing(false)
735+
, m_flushedBytes(0){QTAUDIO_DEBUG("QTAudioOutput")}
716736

717737
QTAudioOutput::~QTAudioOutput()
718738
{
@@ -739,6 +759,8 @@ namespace IPCore
739759
{
740760
QTAUDIO_DEBUG("QTAudioOutput::startAudio check")
741761

762+
doneFlushing();
763+
742764
if (state() == QAudio::StoppedState)
743765
{
744766
m_thread.startOfInitialization();
@@ -753,6 +775,7 @@ namespace IPCore
753775
// again before calling start().
754776
setAudioOutputBufferSize();
755777
#endif
778+
m_ioDevice.stopProducingSilence();
756779
m_thread.setStartSample(0);
757780
m_thread.setProcessedSamples(0);
758781
m_thread.setPreRollDelay(0);
@@ -764,6 +787,7 @@ namespace IPCore
764787
#ifdef PLATFORM_LINUX
765788
m_thread.setPreRollDelay(0);
766789
#endif
790+
m_ioDevice.stopProducingSilence();
767791
resume();
768792
}
769793
}
@@ -783,7 +807,12 @@ namespace IPCore
783807

784808
if (state() != QAudio::StoppedState)
785809
{
810+
#ifdef PLATFORM_LINUX
811+
// On Linux, use suspend() instead of stop() to avoid audio corruption after multiple cycles
812+
suspend();
813+
#else
786814
stop();
815+
#endif
787816
}
788817
}
789818

@@ -797,16 +826,39 @@ namespace IPCore
797826
}
798827
}
799828

829+
void QTAudioOutput::startFlushing()
830+
{
831+
m_isFlushing = true;
832+
m_flushedBytes = 0;
833+
}
834+
800835
void QTAudioOutput::suspendAndResetAudio()
801836
{
802837
QTAUDIO_DEBUG("QTAudioOutput::suspendAndResetAudio")
803838

804839
if (state() != QAudio::StoppedState)
805840
{
841+
#ifdef PLATFORM_LINUX
842+
// On Linux, to avoid audio corruption after multiple cycles, we
843+
// cannot call reset() or suspend() directly as the internal
844+
// buffer will not be cleared, causing audio bleed. Instead,
845+
// we start a flushing process that will feed the sink with
846+
// silence until its buffer is cleared, and then suspend it.
847+
if (state() == QAudio::ActiveState)
848+
{
849+
startFlushing();
850+
}
851+
else
852+
{
853+
suspend();
854+
}
855+
#else
806856
suspend();
857+
// On non-Linux platforms, do a full reset to clear buffers immediately
807858
QTAUDIO_DEBUG("QTAudioOutput::suspendAndResetAudio - before reset")
808859
reset();
809860
QTAUDIO_DEBUG("QTAudioOutput::suspendAndResetAudio - after reset")
861+
#endif
810862
}
811863
}
812864

src/lib/audio/QTAudioRenderer/QTAudioRenderer/QTAudioRenderer.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,17 @@ namespace IPCore
4848

4949
void start();
5050

51+
void startProducingSilence() { m_silence = true; }
52+
53+
void stopProducingSilence() { m_silence = false; }
54+
5155
public slots:
5256
void resetDevice();
5357
void stopDevice();
5458

5559
private:
5660
QTAudioThread& m_thread;
61+
bool m_silence = false;
5762
};
5863

5964
class QTAudioOutput : public QAudioSink
@@ -64,6 +69,16 @@ namespace IPCore
6469
QTAudioOutput(QAudioDevice& audioDevice, QAudioFormat& audioFormat, QTAudioIODevice& ioDevice, QTAudioThread& audioThread);
6570
~QTAudioOutput();
6671

72+
bool isFlushing() const { return m_isFlushing; }
73+
74+
void startFlushing();
75+
76+
void doneFlushing() { m_isFlushing = false; }
77+
78+
void accumulateFlushedBytes(qint64 n) { m_flushedBytes += n; }
79+
80+
bool flushedEnough() const { return m_flushedBytes >= bufferSize(); }
81+
6782
public slots:
6883
void startAudio();
6984
void resetAudio();
@@ -82,6 +97,8 @@ namespace IPCore
8297
QAudioFormat& m_format;
8398
QTAudioIODevice& m_ioDevice;
8499
QTAudioThread& m_thread;
100+
bool m_isFlushing = false;
101+
qint64 m_flushedBytes;
85102
};
86103

87104
class QTAudioThread : public QThread

0 commit comments

Comments
 (0)