summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Blechmann <[email protected]>2025-04-07 11:39:19 +0800
committerTim Blechmann <[email protected]>2025-07-04 01:54:13 +0000
commitcac4f0de9a934891a28e383e6c296df499516651 (patch)
treefd616c617ff4dd8815aac0b79d0ebb8e6f681795
parentce63fb9c4e6c191cccc616219c32684cda4e41bb (diff)
QSoundEffect: use QAudioPlaybackEngine to implement new QSoundEffectHEADdev
QAudioPlaybackEngine can be reused among instances and allows us to implement QSoundEffect in a more efficient and robust manner: * we share a single QAudioPlaybackEngine * we allow multiple sounds to play with the same instance Task-number: QTBUG-127560 Task-number: QTBUG-131912 Task-number: QTBUG-133307 Pick-to: 6.10 Change-Id: I4785d42356f93ca8b3be6a330a32580b51b5ad64 Reviewed-by: Timur Pocheptsov <[email protected]>
-rw-r--r--src/multimedia/CMakeLists.txt1
-rw-r--r--src/multimedia/audio/qsoundeffect.cpp23
-rw-r--r--src/multimedia/audio/qsoundeffectsynchronous_p.h3
-rw-r--r--src/multimedia/audio/qsoundeffectwithplayer.cpp421
-rw-r--r--src/multimedia/audio/qsoundeffectwithplayer_p.h125
-rw-r--r--tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp25
6 files changed, 591 insertions, 7 deletions
diff --git a/src/multimedia/CMakeLists.txt b/src/multimedia/CMakeLists.txt
index 4fcc118bf..bce3a6e21 100644
--- a/src/multimedia/CMakeLists.txt
+++ b/src/multimedia/CMakeLists.txt
@@ -37,6 +37,7 @@ qt_internal_add_module(Multimedia
audio/qaudio_rtsan_support_p.h
audio/qsamplecache_p.cpp audio/qsamplecache_p.h
audio/qsoundeffect.cpp audio/qsoundeffect.h
+ audio/qsoundeffectwithplayer.cpp audio/qsoundeffectwithplayer_p.h
audio/qsoundeffectsynchronous.cpp audio/qsoundeffectsynchronous_p.h
audio/qsoundeffect_p.h
audio/qrtaudioengine.cpp audio/qrtaudioengine_p.h
diff --git a/src/multimedia/audio/qsoundeffect.cpp b/src/multimedia/audio/qsoundeffect.cpp
index 2184620a1..d92120932 100644
--- a/src/multimedia/audio/qsoundeffect.cpp
+++ b/src/multimedia/audio/qsoundeffect.cpp
@@ -10,8 +10,10 @@
#include <QtMultimedia/qaudiodevice.h>
#include <QtMultimedia/qaudiosink.h>
#include <QtMultimedia/qmediadevices.h>
+#include <QtMultimedia/private/qaudiosystem_p.h>
#include <QtMultimedia/private/qsamplecache_p.h>
#include <QtMultimedia/private/qsoundeffectsynchronous_p.h>
+#include <QtMultimedia/private/qsoundeffectwithplayer_p.h>
#include <QtMultimedia/private/qtmultimediaglobal_p.h>
QT_BEGIN_NAMESPACE
@@ -19,6 +21,25 @@ QT_BEGIN_NAMESPACE
Q_APPLICATION_STATIC(QSampleCache, sampleCache)
Q_LOGGING_CATEGORY(qLcSoundEffect, "qt.multimedia.soundeffect")
+namespace {
+
+QSoundEffectPrivate *makeSoundEffectPrivate(QSoundEffect *fx, const QAudioDevice &audioDevice)
+{
+#if defined(Q_OS_MACOS) && defined(Q_OS_WIN)
+ return new QtMultimediaPrivate::QSoundEffectPrivateWithPlayer(fx, audioDevice);
+#endif
+
+ QAudioSink dummySink(audioDevice.isNull() ? QMediaDevices::defaultAudioOutput() : audioDevice);
+ auto platformSink = QPlatformAudioSink::get(dummySink);
+
+ if (platformSink && platformSink->hasCallbackAPI())
+ return new QtMultimediaPrivate::QSoundEffectPrivateWithPlayer(fx, audioDevice);
+ else
+ return new QSoundEffectPrivateSynchronous(fx, audioDevice);
+}
+
+} // namespace
+
/*!
\class QSoundEffect
\brief The QSoundEffect class provides a way to play low latency sound effects.
@@ -94,7 +115,7 @@ QSoundEffect::QSoundEffect(QObject *parent)
Creates a QSoundEffect with the given \a audioDevice and \a parent.
*/
QSoundEffect::QSoundEffect(const QAudioDevice &audioDevice, QObject *parent)
- : QObject(*new QSoundEffectPrivateSynchronous(this, audioDevice), parent)
+ : QObject(*makeSoundEffectPrivate(this, audioDevice), parent)
{
}
diff --git a/src/multimedia/audio/qsoundeffectsynchronous_p.h b/src/multimedia/audio/qsoundeffectsynchronous_p.h
index e2e68203a..fd6d253a1 100644
--- a/src/multimedia/audio/qsoundeffectsynchronous_p.h
+++ b/src/multimedia/audio/qsoundeffectsynchronous_p.h
@@ -22,7 +22,8 @@
QT_BEGIN_NAMESPACE
-class QSoundEffectPrivateSynchronous : public QIODevice, public QSoundEffectPrivate
+class Q_MULTIMEDIA_EXPORT QSoundEffectPrivateSynchronous : public QIODevice,
+ public QSoundEffectPrivate
{
struct AudioSinkDeleter
{
diff --git a/src/multimedia/audio/qsoundeffectwithplayer.cpp b/src/multimedia/audio/qsoundeffectwithplayer.cpp
new file mode 100644
index 000000000..1a9ec7a4a
--- /dev/null
+++ b/src/multimedia/audio/qsoundeffectwithplayer.cpp
@@ -0,0 +1,421 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qsoundeffectwithplayer_p.h"
+
+#include <QtCore/qmutex.h>
+#include <QtCore/q20map.h>
+
+#include <utility>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtMultimediaPrivate {
+
+namespace {
+
+QSpan<const float> toFloatSpan(QSpan<const char> byteArray)
+{
+ return QSpan{
+ reinterpret_cast<const float *>(byteArray.data()),
+ qsizetype(byteArray.size_bytes() / sizeof(float)),
+ };
+}
+
+} // namespace
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+QSoundEffectVoice::QSoundEffectVoice(VoiceId voiceId, std::shared_ptr<const QSample> sample,
+ float volume, bool muted, int totalLoopCount)
+ : QRtAudioEngineVoice{ voiceId },
+ m_sample{ std::move(sample) },
+ m_volume{ volume },
+ m_muted{ muted },
+ m_loopsRemaining{ totalLoopCount }
+{
+}
+
+VoicePlayResult QSoundEffectVoice::play(QSpan<float> outputBuffer) noexcept QT_MM_NONBLOCKING
+{
+ const QAudioFormat &format = m_sample->format();
+ int totalSamples = m_totalFrames * format.channelCount();
+ int currentSample = format.channelCount() * m_currentFrame;
+
+ const QSpan fullSample = toFloatSpan(m_sample->data());
+ QSpan playbackRange = take(drop(fullSample, currentSample), totalSamples);
+
+ Q_ASSERT(!playbackRange.empty());
+
+ // later: (auto)vectorize?
+ qsizetype samplesToPlay = std::min(playbackRange.size(), outputBuffer.size());
+ if (m_muted || m_volume == 0.f) {
+ auto outputRange = take(outputBuffer, samplesToPlay);
+ std::fill(outputRange.begin(), outputRange.end(), 0.f);
+ } else if (m_volume == 1.f) {
+ for (qsizetype i = 0; i != samplesToPlay; ++i)
+ outputBuffer[i] += playbackRange[i];
+ } else {
+ for (qsizetype i = 0; i != samplesToPlay; ++i)
+ outputBuffer[i] += playbackRange[i] * m_volume;
+ }
+
+ m_currentFrame += samplesToPlay / format.channelCount();
+
+ if (m_currentFrame == m_totalFrames) {
+ const bool isInfiniteLoop = loopsRemaining() == QSoundEffect::Infinite;
+ bool continuePlaying = isInfiniteLoop;
+
+ if (!isInfiniteLoop)
+ continuePlaying = m_loopsRemaining.fetch_sub(1, std::memory_order_relaxed) > 1;
+
+ if (continuePlaying) {
+ if (!isInfiniteLoop)
+ m_currentLoopChanged.set();
+ m_currentFrame = 0;
+ QSpan remainingOutputBuffer = drop(outputBuffer, samplesToPlay);
+ return play(remainingOutputBuffer);
+ }
+ return VoicePlayResult::Finished;
+ }
+ return VoicePlayResult::Playing;
+}
+
+bool QSoundEffectVoice::isActive() noexcept QT_MM_NONBLOCKING
+{
+ if (m_currentFrame != m_totalFrames)
+ return true;
+
+ return loopsRemaining() != 0;
+}
+
+std::shared_ptr<QSoundEffectVoice> QSoundEffectVoice::clone() const
+{
+ auto clone = std::make_shared<QSoundEffectVoice>(QRtAudioEngine::allocateVoiceId(), m_sample,
+ m_volume, m_muted, loopsRemaining());
+
+ // caveat: reading frame is not atomic, so we may have a race here ... is is rare, though,
+ // not sure if we really care
+ clone->m_currentFrame = m_currentFrame;
+ return clone;
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+QSoundEffectPrivateWithPlayer::QSoundEffectPrivateWithPlayer(QSoundEffect *q,
+ QAudioDevice audioDevice)
+ : q_ptr{ q }, m_audioDevice{ std::move(audioDevice) }
+{
+ resolveAudioDevice();
+
+ QObject::connect(&m_mediaDevices, &QMediaDevices::audioOutputsChanged, this, [this] {
+ QAudioDevice defaultAudioDevice = QMediaDevices::defaultAudioOutput();
+ if (defaultAudioDevice == m_defaultAudioDevice)
+ return;
+
+ m_defaultAudioDevice = QMediaDevices::defaultAudioOutput();
+ if (m_audioDevice.isNull())
+ setResolvedAudioDevice(m_defaultAudioDevice);
+ });
+}
+
+QSoundEffectPrivateWithPlayer::~QSoundEffectPrivateWithPlayer()
+{
+ stop();
+}
+
+bool QSoundEffectPrivateWithPlayer::setAudioDevice(QAudioDevice device)
+{
+ if (device == m_audioDevice)
+ return false;
+
+ m_audioDevice = std::move(device);
+ resolveAudioDevice();
+ return true;
+}
+
+void QSoundEffectPrivateWithPlayer::setResolvedAudioDevice(QAudioDevice device)
+{
+ if (m_resolvedAudioDevice == device)
+ return;
+
+ m_resolvedAudioDevice = std::move(device);
+
+ if (!m_player)
+ return;
+
+ for (const auto &voice : m_voices)
+ m_player->stop(voice);
+
+ std::vector<std::shared_ptr<QSoundEffectVoice>> voices{
+ std::make_move_iterator(m_voices.begin()), std::make_move_iterator(m_voices.end())
+ };
+ m_voices.clear();
+
+ bool hasPlayer = updatePlayer();
+ if (!hasPlayer)
+ return;
+
+ for (const auto &voice : voices)
+ // we re-allocate a new voice ID and play on the new player
+ play(voice->clone());
+}
+
+void QSoundEffectPrivateWithPlayer::resolveAudioDevice()
+{
+ if (m_audioDevice.isNull())
+ m_defaultAudioDevice = QMediaDevices::defaultAudioOutput();
+ setResolvedAudioDevice(m_audioDevice.isNull() ? m_defaultAudioDevice : m_audioDevice);
+}
+
+QAudioDevice QSoundEffectPrivateWithPlayer::audioDevice() const
+{
+ return m_audioDevice;
+}
+
+bool QSoundEffectPrivateWithPlayer::setSource(const QUrl &url, QSampleCache &sampleCache)
+{
+ if (m_sampleLoadFuture.isValid())
+ m_sampleLoadFuture.cancel();
+
+ m_url = url;
+ m_sample = {};
+
+ if (url.isEmpty()) {
+ setStatus(QSoundEffect::Null);
+ return false;
+ }
+
+ if (!url.isValid()) {
+ setStatus(QSoundEffect::Error);
+ return false;
+ }
+
+ setStatus(QSoundEffect::Loading);
+
+ m_sampleLoadFuture =
+ sampleCache.requestSampleFuture(url).then(this, [this](SharedSamplePtr result) {
+ if (result) {
+ if (!formatIsSupported(result->format())) {
+ qWarning("QSoundEffect: QSoundEffect only supports mono or stereo files");
+ setStatus(QSoundEffect::Error);
+ return;
+ }
+
+ m_sample = std::move(result);
+ setStatus(QSoundEffect::Ready);
+ bool hasPlayer = updatePlayer();
+ if (std::exchange(m_playPending, false)) {
+ if (hasPlayer)
+ play();
+ }
+ } else {
+ qWarning("QSoundEffect: Error decoding source %ls", qUtf16Printable(m_url.toString()));
+ setStatus(QSoundEffect::Error);
+ }
+ });
+
+ return true;
+}
+
+QUrl QSoundEffectPrivateWithPlayer::url() const
+{
+ return m_url;
+}
+
+void QSoundEffectPrivateWithPlayer::setStatus(QSoundEffect::Status status)
+{
+ if (status == m_status)
+ return;
+ m_status = status;
+ emit q_ptr->statusChanged();
+}
+
+QSoundEffect::Status QSoundEffectPrivateWithPlayer::status() const
+{
+ return m_status;
+}
+
+int QSoundEffectPrivateWithPlayer::loopCount() const
+{
+ return m_loopCount;
+}
+
+bool QSoundEffectPrivateWithPlayer::setLoopCount(int loopCount)
+{
+ if (loopCount == 0)
+ loopCount = 1;
+
+ if (loopCount == m_loopCount)
+ return false;
+
+ m_loopCount = loopCount;
+
+ if (m_voices.empty())
+ return true;
+
+ const std::shared_ptr<QSoundEffectVoice> &voice = *m_voices.rbegin();
+ voice->m_loopsRemaining.store(loopCount, std::memory_order_relaxed);
+
+ setLoopsRemaining(loopCount);
+
+ return true;
+}
+
+int QSoundEffectPrivateWithPlayer::loopsRemaining() const
+{
+ if (m_voices.empty())
+ return 0;
+
+ return m_loopsRemaining;
+}
+
+float QSoundEffectPrivateWithPlayer::volume() const
+{
+ return m_volume;
+}
+
+bool QSoundEffectPrivateWithPlayer::setVolume(float volume)
+{
+ if (m_volume == volume)
+ return false;
+
+ m_volume = volume;
+ for (const auto &voice : m_voices) {
+ m_player->visitVoiceRt(voice, [volume](QSoundEffectVoice &voice) {
+ voice.m_volume = volume;
+ });
+ }
+ return true;
+}
+
+bool QSoundEffectPrivateWithPlayer::muted() const
+{
+ return m_muted;
+}
+
+bool QSoundEffectPrivateWithPlayer::setMuted(bool muted)
+{
+ if (m_muted == muted)
+ return false;
+
+ m_muted = muted;
+ for (const auto &voice : m_voices) {
+ m_player->visitVoiceRt(voice, [muted](QSoundEffectVoice &voice) {
+ voice.m_muted = muted;
+ });
+ }
+ return true;
+}
+
+void QSoundEffectPrivateWithPlayer::play()
+{
+ if (!m_sample) {
+ m_playPending = true;
+ return;
+ }
+
+ // each `play` will start a new voice
+ Q_ASSERT(m_player);
+
+ auto voice = std::make_shared<QSoundEffectVoice>(QRtAudioEngine::allocateVoiceId(), m_sample,
+ m_volume, m_muted, m_loopCount);
+
+ play(std::move(voice));
+}
+
+void QSoundEffectPrivateWithPlayer::stop()
+{
+ size_t activeVoices = m_voices.size();
+ for (const auto &voice : m_voices)
+ m_player->stop(voice->voiceId());
+ setLoopsRemaining(0);
+
+ m_voices.clear();
+ m_playPending = false;
+ if (activeVoices)
+ emit q_ptr->playingChanged();
+}
+
+bool QSoundEffectPrivateWithPlayer::playing() const
+{
+ return !m_voices.empty();
+}
+
+void QSoundEffectPrivateWithPlayer::play(std::shared_ptr<QSoundEffectVoice> voice)
+{
+ QObject::connect(&voice->m_currentLoopChanged, &QAutoResetEvent::activated, this,
+ [this, voiceId = voice->voiceId()] {
+ auto foundVoice = m_voices.find(voiceId);
+ if (foundVoice == m_voices.end())
+ return;
+
+ if (voiceId != activeVoice())
+ return;
+
+ setLoopsRemaining((*foundVoice)->loopsRemaining());
+ });
+
+ m_player->play(voice);
+ m_voices.insert(std::move(voice));
+ setLoopsRemaining(m_loopCount);
+ if (m_voices.size() == 1)
+ emit q_ptr->playingChanged();
+}
+
+bool QSoundEffectPrivateWithPlayer::updatePlayer()
+{
+ Q_ASSERT(m_voices.empty());
+ QObject::disconnect(m_voiceFinishedConnection);
+
+ m_player = {};
+ if (m_resolvedAudioDevice.isNull())
+ return false;
+
+ auto player = QRtAudioEngine::getEngineFor(m_resolvedAudioDevice, m_sample->format());
+ m_player = player;
+
+ m_voiceFinishedConnection = QObject::connect(m_player.get(), &QRtAudioEngine::voiceFinished,
+ this, [this](VoiceId voiceId) {
+ if (voiceId == activeVoice())
+ setLoopsRemaining(0);
+
+ auto found = m_voices.find(voiceId);
+ if (found != m_voices.end()) {
+ m_voices.erase(found);
+ if (m_voices.empty())
+ emit q_ptr->playingChanged();
+ }
+ });
+ return true;
+}
+
+std::optional<VoiceId> QSoundEffectPrivateWithPlayer::activeVoice() const
+{
+ if (m_voices.empty())
+ return std::nullopt;
+ return (*m_voices.rbegin())->voiceId();
+}
+
+bool QSoundEffectPrivateWithPlayer::formatIsSupported(const QAudioFormat &fmt)
+{
+ switch (fmt.channelCount()) {
+ case 1:
+ case 2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+void QSoundEffectPrivateWithPlayer::setLoopsRemaining(int loopsRemaining)
+{
+ if (loopsRemaining == m_loopsRemaining)
+ return;
+ m_loopsRemaining = loopsRemaining;
+ emit q_ptr->loopsRemainingChanged();
+}
+
+} // namespace QtMultimediaPrivate
+
+QT_END_NAMESPACE
diff --git a/src/multimedia/audio/qsoundeffectwithplayer_p.h b/src/multimedia/audio/qsoundeffectwithplayer_p.h
new file mode 100644
index 000000000..577df8e52
--- /dev/null
+++ b/src/multimedia/audio/qsoundeffectwithplayer_p.h
@@ -0,0 +1,125 @@
+// Copyright (C) 2025 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#ifndef QSOUNDEFFECTWITHPLAYER_P_H
+#define QSOUNDEFFECTWITHPLAYER_P_H
+
+//
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+//
+
+#include <QtMultimedia/qaudiosink.h>
+#include <QtMultimedia/qmediadevices.h>
+#include <QtMultimedia/private/qaudiosystem_p.h>
+#include <QtMultimedia/private/qautoresetevent_p.h>
+#include <QtMultimedia/private/qrtaudioengine_p.h>
+#include <QtMultimedia/private/qsoundeffect_p.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace QtMultimediaPrivate {
+
+using QtPrivate::QAutoResetEvent;
+
+class QSoundEffectVoice final : public QRtAudioEngineVoice
+{
+public:
+ QSoundEffectVoice(VoiceId voiceId, std::shared_ptr<const QSample> sample, float volume,
+ bool muted, int totalLoopCount);
+
+ VoicePlayResult play(QSpan<float>) noexcept QT_MM_NONBLOCKING override;
+ bool isActive() noexcept QT_MM_NONBLOCKING override;
+ const QAudioFormat &format() noexcept override { return m_sample->format(); }
+
+ int loopsRemaining() const { return m_loopsRemaining.load(std::memory_order_relaxed); }
+ std::shared_ptr<QSoundEffectVoice> clone() const;
+
+ const std::shared_ptr<const QSample> m_sample;
+ const int m_totalFrames{
+ m_sample->format().framesForBytes(m_sample->data().size()),
+ };
+
+ float m_volume{};
+ bool m_muted{};
+
+ std::atomic_int m_loopsRemaining;
+ int m_currentFrame{};
+
+ QAutoResetEvent m_currentLoopChanged;
+};
+
+// Design notes
+// * each play() will start a new voice
+// * stop() will stop all voices
+// * the most recently created voice can be obtained via `activeVoice()`
+// * mute/volume will affect all voices
+// * loopCount/loopRemaining will be obtained from most recently created voice
+class QSoundEffectPrivateWithPlayer final : public QObject, public QSoundEffectPrivate
+{
+public:
+ QSoundEffectPrivateWithPlayer(QSoundEffect *q, QAudioDevice audioDevice);
+ Q_DISABLE_COPY_MOVE(QSoundEffectPrivateWithPlayer)
+ ~QSoundEffectPrivateWithPlayer() override;
+
+ // QSoundEffectPrivate interface
+ bool setAudioDevice(QAudioDevice device) override;
+ QAudioDevice audioDevice() const override;
+ bool setSource(const QUrl &, QSampleCache &) override;
+ QUrl url() const override;
+ QSoundEffect::Status status() const override;
+ int loopCount() const override;
+ bool setLoopCount(int) override;
+ int loopsRemaining() const override;
+ float volume() const override;
+ bool setVolume(float) override;
+ bool muted() const override;
+ bool setMuted(bool) override;
+ void play() override;
+ void stop() override;
+ bool playing() const override;
+
+private:
+ void play(std::shared_ptr<QSoundEffectVoice>);
+ void setStatus(QSoundEffect::Status status);
+ [[nodiscard]] bool updatePlayer();
+ std::optional<VoiceId> activeVoice() const;
+ static bool formatIsSupported(const QAudioFormat &);
+ void setResolvedAudioDevice(QAudioDevice device);
+ void resolveAudioDevice();
+
+ QSoundEffect *q_ptr{};
+ QAudioDevice m_audioDevice;
+ QAudioDevice m_resolvedAudioDevice;
+ std::shared_ptr<QRtAudioEngine> m_player;
+ QMetaObject::Connection m_voiceFinishedConnection;
+ std::set<std::shared_ptr<QSoundEffectVoice>, QRtAudioEngineVoiceCompare> m_voices;
+
+ void setLoopsRemaining(int);
+ int m_loopsRemaining{ 0 };
+
+ QFuture<void> m_sampleLoadFuture;
+ QUrl m_url;
+ SharedSamplePtr m_sample;
+ float m_volume = 1.f;
+ bool m_muted = false;
+ int m_loopCount = 1;
+ bool m_playPending = false;
+
+ QSoundEffect::Status m_status{};
+
+ QMediaDevices m_mediaDevices;
+ QAudioDevice m_defaultAudioDevice;
+};
+
+} // namespace QtMultimediaPrivate
+
+QT_END_NAMESPACE
+
+#endif // QSOUNDEFFECTWITHPLAYER_P_H
diff --git a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
index 65d4748db..d67d55e5a 100644
--- a/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
+++ b/tests/auto/integration/qsoundeffect/tst_qsoundeffect.cpp
@@ -7,6 +7,8 @@
#include <QtMultimedia/qmediadevices.h>
#include <QtMultimedia/qsoundeffect.h>
+#include <QtMultimedia/private/qsoundeffectsynchronous_p.h>
+
using namespace Qt::Literals;
class tst_QSoundEffect : public QObject
@@ -128,7 +130,7 @@ void tst_QSoundEffect::testLooping()
QTestEventLoop::instance().enterLoop(3);
QTRY_COMPARE(sound->loopsRemaining(), 0);
- QVERIFY(readSignal_Remaining.size() == 4);
+ QCOMPARE(readSignal_Remaining.size(), 4);
QTRY_VERIFY(!sound->isPlaying());
// QTBUG-36643 (setting the loop count while playing should work)
@@ -218,6 +220,8 @@ void tst_QSoundEffect::testMuting()
void tst_QSoundEffect::testPlaying()
{
+ QSignalSpy playingChangedSignal(sound, &QSoundEffect::playingChanged);
+
sound->setLoopCount(QSoundEffect::Infinite);
sound->setVolume(0.1f);
//valid source
@@ -226,14 +230,18 @@ void tst_QSoundEffect::testPlaying()
sound->play();
QTestEventLoop::instance().enterLoop(1);
QTRY_COMPARE(sound->isPlaying(), true);
+ QCOMPARE(playingChangedSignal.size(), 1);
sound->stop();
+ QCOMPARE(playingChangedSignal.size(), 2);
//empty source
+ playingChangedSignal.clear();
sound->setSource(QUrl());
QTestEventLoop::instance().enterLoop(1);
sound->play();
QTestEventLoop::instance().enterLoop(1);
QTRY_COMPARE(sound->isPlaying(), false);
+ QCOMPARE(playingChangedSignal.size(), 0);
//invalid source
sound->setSource(QUrl((QLatin1String("invalid source"))));
@@ -395,7 +403,7 @@ void tst_QSoundEffect::testCorruptFile()
{
using namespace Qt::Literals;
auto expectedMessagePattern =
- QRegularExpression(uR"(^QSoundEffect\(qaudio\): Error decoding source .*$)"_s);
+ QRegularExpression(uR"(^QSoundEffect.*: Error decoding source .*$)"_s);
for (int i = 0; i < 10; i++) {
QSignalSpy statusSpy(sound, &QSoundEffect::statusChanged);
@@ -421,9 +429,16 @@ void tst_QSoundEffect::setAudioDevice_emitsSignalsInExpectedOrder_data()
QTest::addColumn<bool>("while_playing");
QTest::addColumn<bool>("with_source");
QTest::addColumn<QStringList>("expectedSignals");
- QTest::addRow("while_playing")
- << true << true
- << QStringList{ u"playingChanged"_s, u"playingChanged"_s, u"audioDeviceChanged"_s };
+ QSoundEffect sfx(this);
+
+ if (dynamic_cast<QSoundEffectPrivateSynchronous *>(QSoundEffectPrivate::get(&sfx))) {
+ QTest::addRow("while_playing")
+ << true << true
+ << QStringList{ u"playingChanged"_s, u"playingChanged"_s, u"audioDeviceChanged"_s };
+ } else {
+ QTest::addRow("while_playing")
+ << true << true << QStringList{ u"playingChanged"_s, u"audioDeviceChanged"_s };
+ }
QTest::addRow("while_stopped, with source")
<< false << true << QStringList{ u"audioDeviceChanged"_s };
QTest::addRow("while_stopped, without source")