diff options
author | Artem Dyomin <artem.dyomin@qt.io> | 2023-03-02 20:54:15 +0100 |
---|---|---|
committer | Qt Cherry-pick Bot <cherrypick_bot@qt-project.org> | 2023-03-05 06:20:50 +0000 |
commit | a16dd33776aca2b80668edaa817d8ed6ae244860 (patch) | |
tree | ac61a3fe535e408e664643c5c512bb1edbbf95e1 | |
parent | 910676cd84c02ca9450bb74b16b3be5e31fcd837 (diff) | |
download | qtmultimedia-a16dd33776aca2b80668edaa817d8ed6ae244860.tar.gz |
Fix audiosink issues on darwin
Fixed problems:
- Fix sound stucks on multiple resets/starts of QAudioSink.
It was possible to reproduce on playback position change in
mediaplayer.
- Improve audiosink stop (reduce waiting time). The optimization is
based on the fact that it's possible to call AudioOutputUnitStop from
the thread where it was started.
- add some auto test + imrove errors logging in tests. Tests work
fine locally but still need some tune on CI
Task-number: QTBUG-111567
Change-Id: I0eb5c32af4c12dfc0694ee8f5967b4960a0b4ab2
Reviewed-by: Doris Verria <doris.verria@qt.io>
(cherry picked from commit 919b3d308b711c0b267808c783327f2c95233428)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
4 files changed, 171 insertions, 45 deletions
diff --git a/src/multimedia/darwin/qdarwinaudiosink.mm b/src/multimedia/darwin/qdarwinaudiosink.mm index 56bfe5397..39063f1ca 100644 --- a/src/multimedia/darwin/qdarwinaudiosink.mm +++ b/src/multimedia/darwin/qdarwinaudiosink.mm @@ -104,7 +104,7 @@ int QDarwinAudioSinkBuffer::available() const void QDarwinAudioSinkBuffer::reset() { m_buffer->reset(); - m_device = 0; + m_device = nullptr; m_deviceError = false; } @@ -583,7 +583,7 @@ bool QDarwinAudioSink::open() void QDarwinAudioSink::close() { if (m_audioUnit != 0) { - AudioOutputUnitStop(m_audioUnit); + audioDeviceStop(); AudioUnitUninitialize(m_audioUnit); AudioComponentInstanceDispose(m_audioUnit); } @@ -593,32 +593,61 @@ void QDarwinAudioSink::close() void QDarwinAudioSink::audioThreadStart() { - QMutexLocker lock(&m_mutex); startTimers(); - m_audioThreadState.storeRelaxed(Running); - AudioOutputUnitStart(m_audioUnit); + audioDeviceStart(); } void QDarwinAudioSink::audioThreadStop() { - QMutexLocker lock(&m_mutex); stopTimers(); - if (m_audioThreadState.testAndSetAcquire(Running, Stopped)) - m_threadFinished.wait(&m_mutex, 500); + + // It's common practice to call AudioOutputUnitStop + // from the thread where the audio output was started, + // so we don't have to rely on the stops inside renderCallback. + audioDeviceStop(); } void QDarwinAudioSink::audioThreadDrain() { - QMutexLocker lock(&m_mutex); stopTimers(); - if (m_audioThreadState.testAndSetAcquire(Running, Draining)) - m_threadFinished.wait(&m_mutex, 500); + + QMutexLocker lock(&m_mutex); + + if (m_audioThreadState.testAndSetAcquire(Running, Draining)) { + constexpr int MaxDrainWaitingTime = 500; + + m_threadFinished.wait(&m_mutex, MaxDrainWaitingTime); + + if (m_audioThreadState.fetchAndStoreRelaxed(Stopped) != Stopped) { + qWarning() << "Couldn't wait for draining; force stop"; + + AudioOutputUnitStop(m_audioUnit); + } + } +} + +void QDarwinAudioSink::audioDeviceStart() +{ + QMutexLocker lock(&m_mutex); + + const auto state = m_audioThreadState.loadAcquire(); + if (state == Stopped) { + m_audioThreadState.storeRelaxed(Running); + AudioOutputUnitStart(m_audioUnit); + } else { + qWarning() << "Unexpected state on audio device start:" << state; + } } void QDarwinAudioSink::audioDeviceStop() { - AudioOutputUnitStop(m_audioUnit); - m_audioThreadState.storeRelaxed(Stopped); + { + QMutexLocker lock(&m_mutex); + + AudioOutputUnitStop(m_audioUnit); + m_audioThreadState.storeRelaxed(Stopped); + } + m_threadFinished.wakeOne(); } @@ -627,10 +656,11 @@ void QDarwinAudioSink::audioDeviceIdle() if (m_stateCode != QAudio::ActiveState) return; - audioDeviceStop(); - m_errorCode = QAudio::UnderrunError; m_stateCode = QAudio::IdleState; + + audioDeviceStop(); + QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection); } @@ -639,10 +669,11 @@ void QDarwinAudioSink::audioDeviceError() if (m_stateCode != QAudio::ActiveState) return; - audioDeviceStop(); - m_errorCode = QAudio::IOError; m_stateCode = QAudio::StoppedState; + + audioDeviceStop(); + QMetaObject::invokeMethod(this, "deviceStopped", Qt::QueuedConnection); } diff --git a/src/multimedia/darwin/qdarwinaudiosink_p.h b/src/multimedia/darwin/qdarwinaudiosink_p.h index f929d36d9..b0ae4c05e 100644 --- a/src/multimedia/darwin/qdarwinaudiosink_p.h +++ b/src/multimedia/darwin/qdarwinaudiosink_p.h @@ -133,6 +133,7 @@ private: void audioThreadStart(); void audioThreadStop(); void audioThreadDrain(); + void audioDeviceStart(); void audioDeviceStop(); void audioDeviceIdle(); void audioDeviceError(); diff --git a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer.cpp b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer.cpp index e75d86b80..55553ec01 100644 --- a/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer.cpp +++ b/src/plugins/multimedia/ffmpeg/playbackengine/qffmpegaudiorenderer.cpp @@ -106,8 +106,9 @@ void AudioRenderer::initResempler(const Codec *codec) void AudioRenderer::freeOutput() { if (m_sink) { - m_sink->stop(); m_sink->reset(); + + // TODO: inestigate if it's enough to reset the sink without deleting m_sink.reset(); } diff --git a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp index d9a318e83..46915ec9b 100644 --- a/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp +++ b/tests/auto/integration/qaudiosink/tst_qaudiosink.cpp @@ -50,6 +50,8 @@ private slots: void pushSuspendResume_data(){generate_audiofile_testrows();} void pushSuspendResume(); + void pushResetResume(); + void pushUnderrun_data(){generate_audiofile_testrows();} void pushUnderrun(); @@ -59,8 +61,9 @@ private slots: private: using FilePtr = QSharedPointer<QFile>; - QString formatToFileName(const QAudioFormat &format); + static QString formatToFileName(const QAudioFormat &format); void createSineWaveData(const QAudioFormat &format, qint64 length, int sampleRate = 440); + static QString dumpStateSignalSpy(const QSignalSpy &stateSignalSpy); void generate_audiofile_testrows(); @@ -130,6 +133,19 @@ void tst_QAudioSink::createSineWaveData(const QAudioFormat &format, qint64 lengt Q_ASSERT(m_buffer->open(QIODevice::ReadOnly)); } +QString tst_QAudioSink::dumpStateSignalSpy(const QSignalSpy& stateSignalSpy) { + QString result = "["; + bool first = true; + for (auto& params : stateSignalSpy) + { + if (!std::exchange(first, false)) + result += ','; + result += QString::number(params.front().value<QAudio::State>()); + } + result.append(']'); + return result; +} + void tst_QAudioSink::generate_audiofile_testrows() { QTest::addColumn<FilePtr>("audioFile"); @@ -384,7 +400,10 @@ void tst_QAudioSink::pull() // Check that QAudioSink immediately transitions to ActiveState QTRY_VERIFY2((stateSignal.size() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit signal on start(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); stateSignal.clear(); @@ -396,7 +415,7 @@ void tst_QAudioSink::pull() // Wait until playback finishes QTRY_VERIFY2(audioFile->atEnd(), "didn't play to EOF"); QTRY_VERIFY(stateSignal.size() > 0); - QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState); + QTRY_COMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); @@ -405,7 +424,10 @@ void tst_QAudioSink::pull() audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.size() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit StoppedState signal after stop(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); @@ -436,11 +458,15 @@ void tst_QAudioSink::pullSuspendResume() audioOutput.start(audioFile.data()); // Check that QAudioSink immediately transitions to ActiveState QTRY_VERIFY2((stateSignal.size() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit signal on start(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after start()"); - QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); - stateSignal.clear(); + QVERIFY2((audioOutput.error() == QAudio::NoError), + "error state is not equal to QAudio::NoError after start()"); + stateSignal.clear(); // Wait for half of clip to play QTest::qWait(500); @@ -449,7 +475,7 @@ void tst_QAudioSink::pullSuspendResume() QTRY_VERIFY2((stateSignal.size() == 1), QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead") - .arg(stateSignal.size()).toUtf8().constData()); + .arg(dumpStateSignalSpy(stateSignal)).toUtf8().constData()); QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()"); stateSignal.clear(); @@ -465,7 +491,10 @@ void tst_QAudioSink::pullSuspendResume() // Check that QAudioSink immediately transitions to ActiveState QVERIFY2((stateSignal.size() == 1), - QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit signal after resume(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after resume()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()"); stateSignal.clear(); @@ -473,7 +502,7 @@ void tst_QAudioSink::pullSuspendResume() // Wait until playback finishes QTRY_VERIFY2(audioFile->atEnd(), "didn't play to EOF"); QTRY_VERIFY(stateSignal.size() > 0); - QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState); + QTRY_COMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); @@ -482,7 +511,10 @@ void tst_QAudioSink::pullSuspendResume() audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.size() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit StoppedState signal after stop(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); @@ -584,7 +616,10 @@ void tst_QAudioSink::push() // Check that QAudioSink immediately transitions to IdleState QTRY_VERIFY2((stateSignal.size() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit signal on start(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); stateSignal.clear(); @@ -607,7 +642,7 @@ void tst_QAudioSink::push() // Check for transition to ActiveState when data is provided QVERIFY2((stateSignal.size() == 1), QString("didn't emit signal after receiving data, got %1 signals instead") - .arg(stateSignal.size()).toUtf8().constData()); + .arg(dumpStateSignalSpy(stateSignal)).toUtf8().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data"); firstBuffer = false; @@ -621,7 +656,7 @@ void tst_QAudioSink::push() QVERIFY2(audioFile->atEnd(), "didn't play to EOF"); QTRY_VERIFY(audioOutput.state() == QAudio::IdleState); QTRY_VERIFY(stateSignal.size() > 0); - QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState); + QTRY_COMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); @@ -630,7 +665,7 @@ void tst_QAudioSink::push() audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.size() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(dumpStateSignalSpy(stateSignal)).toUtf8().constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); @@ -663,7 +698,10 @@ void tst_QAudioSink::pushSuspendResume() // Check that QAudioSink immediately transitions to IdleState QTRY_VERIFY2((stateSignal.size() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit signal on start(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); stateSignal.clear(); @@ -687,7 +725,7 @@ void tst_QAudioSink::pushSuspendResume() // Check for transition to ActiveState when data is provided QVERIFY2((stateSignal.size() == 1), QString("didn't emit signal after receiving data, got %1 signals instead") - .arg(stateSignal.size()).toUtf8().constData()); + .arg(dumpStateSignalSpy(stateSignal)).toUtf8().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data"); firstBuffer = false; @@ -702,7 +740,7 @@ void tst_QAudioSink::pushSuspendResume() QTRY_VERIFY2((stateSignal.size() == 1), QString("didn't emit SuspendedState signal after suspend(), got %1 signals instead") - .arg(stateSignal.size()).toUtf8().constData()); + .arg(dumpStateSignalSpy(stateSignal)).toUtf8().constData()); QVERIFY2((audioOutput.state() == QAudio::SuspendedState), "didn't transition to SuspendedState after suspend()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after suspend()"); stateSignal.clear(); @@ -722,7 +760,10 @@ void tst_QAudioSink::pushSuspendResume() // Check that QAudioSink immediately transitions to IdleState QVERIFY2((stateSignal.size() == 1), - QString("didn't emit signal after resume(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit signal after resume(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == suspendedInState), "resume() didn't transition to state before suspend()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after resume()"); stateSignal.clear(); @@ -741,7 +782,7 @@ void tst_QAudioSink::pushSuspendResume() QVERIFY2(audioFile->atEnd(), "didn't play to EOF"); QTRY_VERIFY(stateSignal.size() > 0); - QCOMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState); + QTRY_COMPARE(qvariant_cast<QAudio::State>(stateSignal.last().at(0)), QAudio::IdleState); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); @@ -750,7 +791,10 @@ void tst_QAudioSink::pushSuspendResume() audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.size() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit StoppedState signal after stop(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); @@ -759,6 +803,46 @@ void tst_QAudioSink::pushSuspendResume() audioFile->close(); } +void tst_QAudioSink::pushResetResume() +{ + auto audioFile = audioFiles.at(0); + auto audioFormat = testFormats.at(0); + + QAudioSink audioOutput(audioFormat, this); + + audioOutput.setBufferSize(8192); + audioOutput.setVolume(0.1f); + + audioFile->close(); + audioFile->open(QIODevice::ReadOnly); + audioFile->seek(QWaveDecoder::headerLength()); + + QPointer<QIODevice> feed = audioOutput.start(); + + QTest::qWait(20); + + auto buffer = audioFile->read(audioOutput.bytesFree()); + feed->write(buffer); + + QTest::qWait(20); + QTRY_COMPARE(audioOutput.state(), QAudio::ActiveState); + + audioOutput.reset(); + QCOMPARE(audioOutput.state(), QAudio::StoppedState); + QCOMPARE(audioOutput.error(), QAudio::NoError); + + const auto processedUSecs = audioOutput.processedUSecs(); + + audioOutput.resume(); + QTest::qWait(40); + + // Nothing changed if resume after reset + QCOMPARE(audioOutput.state(), QAudio::StoppedState); + QCOMPARE(audioOutput.error(), QAudio::NoError); + + QCOMPARE(audioOutput.processedUSecs(), processedUSecs); +} + void tst_QAudioSink::pushUnderrun() { QFETCH(FilePtr, audioFile); @@ -783,7 +867,10 @@ void tst_QAudioSink::pushUnderrun() // Check that QAudioSink immediately transitions to IdleState QTRY_VERIFY2((stateSignal.size() == 1), - QString("didn't emit signal on start(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit signal on start(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState after start()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after start()"); stateSignal.clear(); @@ -808,7 +895,7 @@ void tst_QAudioSink::pushUnderrun() // Check for transition to ActiveState when data is provided QVERIFY2((stateSignal.size() == 1), QString("didn't emit signal after receiving data, got %1 signals instead") - .arg(stateSignal.size()).toUtf8().constData()); + .arg(dumpStateSignalSpy(stateSignal)).toUtf8().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data"); firstBuffer = false; @@ -823,7 +910,7 @@ void tst_QAudioSink::pushUnderrun() QVERIFY2((stateSignal.size() == 1), QString("didn't emit IdleState signal after suspend(), got %1 signals instead") - .arg(stateSignal.size()).toUtf8().constData()); + .arg(dumpStateSignalSpy(stateSignal)).toUtf8().constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transition to IdleState, no data"); QVERIFY2((audioOutput.error() == QAudio::UnderrunError), "error state is not equal to QAudio::UnderrunError, no data"); stateSignal.clear(); @@ -838,7 +925,7 @@ void tst_QAudioSink::pushUnderrun() // Check for transition to ActiveState when data is provided QVERIFY2((stateSignal.size() == 1), QString("didn't emit signal after receiving data, got %1 signals instead") - .arg(stateSignal.size()).toUtf8().constData()); + .arg(dumpStateSignalSpy(stateSignal)).toUtf8().constData()); QVERIFY2((audioOutput.state() == QAudio::ActiveState), "didn't transition to ActiveState after receiving data"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error state is not equal to QAudio::NoError after receiving data"); firstBuffer = false; @@ -851,7 +938,10 @@ void tst_QAudioSink::pushUnderrun() // Wait until playback finishes QVERIFY2(audioFile->atEnd(), "didn't play to EOF"); QTRY_VERIFY2((stateSignal.size() == 1), - QString("didn't emit IdleState signal when at EOF, got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit IdleState signal when at EOF, got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::IdleState), "didn't transitions to IdleState when at EOF"); stateSignal.clear(); @@ -860,7 +950,10 @@ void tst_QAudioSink::pushUnderrun() audioOutput.stop(); QTest::qWait(40); QVERIFY2((stateSignal.size() == 1), - QString("didn't emit StoppedState signal after stop(), got %1 signals instead").arg(stateSignal.size()).toUtf8().constData()); + QString("didn't emit StoppedState signal after stop(), got %1 signals instead") + .arg(dumpStateSignalSpy(stateSignal)) + .toUtf8() + .constData()); QVERIFY2((audioOutput.state() == QAudio::StoppedState), "didn't transitions to StoppedState after stop()"); QVERIFY2((audioOutput.error() == QAudio::NoError), "error() is not QAudio::NoError after stop()"); |