summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTim Blechmann <[email protected]>2025-03-28 16:02:45 +0800
committerTim Blechmann <[email protected]>2025-06-05 07:57:42 +0800
commit45d3cd3ef0747194921ec96ea1c9cefc9ca04da6 (patch)
tree01e3c1dd3758861108a1c23983551a7be2d77744
parent10b93774245065453696adb56fcc9bdf734593a0 (diff)
QSampleCache: use promises to simplify object connectionsHEADdev
Pick-to: 6.10 Task-number: QTBUG-129465 Change-Id: Ie6f7e83ed448c3ee24b241c0543570c27c7d9cac Reviewed-by: Artem Dyomin <[email protected]>
-rw-r--r--src/multimedia/audio/qsamplecache_p.cpp68
-rw-r--r--src/multimedia/audio/qsamplecache_p.h12
-rw-r--r--src/multimedia/audio/qsoundeffect.cpp77
3 files changed, 102 insertions, 55 deletions
diff --git a/src/multimedia/audio/qsamplecache_p.cpp b/src/multimedia/audio/qsamplecache_p.cpp
index 5651e0e7f..f9837c240 100644
--- a/src/multimedia/audio/qsamplecache_p.cpp
+++ b/src/multimedia/audio/qsamplecache_p.cpp
@@ -115,7 +115,7 @@ QSampleCache::~QSampleCache()
// deleted using deleteLater. And some samples that had deleteLater
// already called won't have been processed (m_staleSamples)
for (auto it = m_samples.cbegin(), end = m_samples.cend(); it != end; ++it)
- delete it.value();
+ delete it->second;
const auto copyStaleSamples = m_staleSamples; //deleting a sample does affect the m_staleSamples list, but we create a copy
for (QSample* sample : copyStaleSamples)
@@ -150,7 +150,7 @@ bool QSampleCache::isLoading() const
bool QSampleCache::isCached(const QUrl &url) const
{
const std::lock_guard<QRecursiveMutex> locker(m_mutex);
- return m_samples.contains(url);
+ return m_samples.find(url) != m_samples.end();
}
QSample* QSampleCache::requestSample(const QUrl& url)
@@ -163,7 +163,7 @@ QSample* QSampleCache::requestSample(const QUrl& url)
qCDebug(qLcSampleCache) << "QSampleCache: request sample [" << url << "]";
std::unique_lock<QRecursiveMutex> locker(m_mutex);
- QMap<QUrl, QSample*>::iterator it = m_samples.find(url);
+ auto it = m_samples.find(url);
QSample* sample;
if (it == m_samples.end()) {
if (needsThreadStart) {
@@ -172,12 +172,12 @@ QSample* QSampleCache::requestSample(const QUrl& url)
m_loadingThread.start();
}
sample = new QSample(url, this);
- m_samples.insert(url, sample);
+ m_samples.emplace(url, sample);
#if QT_CONFIG(thread)
sample->moveToThread(&m_loadingThread);
#endif
} else {
- sample = *it;
+ sample = it->second;
if (sample->state() == QSample::Error && needsThreadStart) {
m_loadingThread.wait();
m_loadingThread.start();
@@ -191,6 +191,53 @@ QSample* QSampleCache::requestSample(const QUrl& url)
return sample;
}
+QFuture<QMaybe<QSample *, QSample::State>> QSampleCache::requestSampleFuture(const QUrl &url)
+{
+ QSample *requestedSample = requestSample(url);
+ auto promise = std::make_shared<QPromise<QMaybe<QSample *, QSample::State>>>();
+ auto future = promise->future();
+
+ switch (requestedSample->m_state) {
+ case QSample::Ready:
+ promise->start();
+ promise->addResult(requestedSample);
+ promise->finish();
+ return future;
+
+ case QSample::Error:
+ promise->start();
+ promise->addResult(QUnexpected{ QSample::Error });
+ promise->finish();
+ return future;
+ default:
+ break;
+ }
+
+ // HACK: we keep track of the context to disconnect on ready or error
+ QObject *context = new QObject(this);
+
+ QObject::connect(requestedSample, &QSample::error, context,
+ [this, promise](QSample *requestedSample) {
+ promise->start();
+ promise->addResult(QUnexpected{ requestedSample->m_state });
+ promise->finish();
+
+ this->m_pendingPromises.erase(promise);
+ });
+
+ QObject::connect(requestedSample, &QSample::ready, context,
+ [this, promise](QSample *requestedSample) {
+ promise->start();
+ promise->addResult(requestedSample);
+ promise->finish();
+ this->m_pendingPromises.erase(promise);
+ });
+
+ m_pendingPromises.emplace(std::move(promise), context);
+
+ return future;
+}
+
void QSampleCache::setCapacity(qint64 capacity)
{
const std::lock_guard<QRecursiveMutex> locker(m_mutex);
@@ -198,8 +245,8 @@ void QSampleCache::setCapacity(qint64 capacity)
return;
qCDebug(qLcSampleCache) << "QSampleCache: capacity changes from " << m_capacity << "to " << capacity;
if (m_capacity > 0 && capacity <= 0) { //memory management strategy changed
- for (QMap<QUrl, QSample*>::iterator it = m_samples.begin(); it != m_samples.end();) {
- QSample* sample = *it;
+ for (auto it = m_samples.begin(); it != m_samples.end();) {
+ QSample *sample = it->second;
if (sample->m_ref == 0) {
unloadSample(sample);
it = m_samples.erase(it);
@@ -232,8 +279,8 @@ void QSampleCache::refresh(qint64 usageChange)
qint64 recoveredSize = 0;
//free unused samples to keep usage under capacity limit.
- for (QMap<QUrl, QSample*>::iterator it = m_samples.begin(); it != m_samples.end();) {
- QSample* sample = *it;
+ for (auto it = m_samples.begin(); it != m_samples.end();) {
+ QSample *sample = it->second;
if (sample->m_ref > 0) {
++it;
continue;
@@ -294,7 +341,7 @@ bool QSampleCache::notifyUnreferencedSample(QSample* sample)
if (m_capacity > 0)
return false;
- m_samples.remove(sample->m_url);
+ m_samples.erase(sample->m_url);
unloadSample(sample);
return true;
}
@@ -413,6 +460,7 @@ void QSample::handleLoadingError(int errorCode)
cleanup();
m_state = QSample::Error;
m_parent->loadingRelease();
+
emit error(this);
}
diff --git a/src/multimedia/audio/qsamplecache_p.h b/src/multimedia/audio/qsamplecache_p.h
index e56753cc6..8cda6db5f 100644
--- a/src/multimedia/audio/qsamplecache_p.h
+++ b/src/multimedia/audio/qsamplecache_p.h
@@ -15,15 +15,18 @@
// We mean it.
//
-#include <QtCore/qmap.h>
#include <QtCore/qmutex.h>
#include <QtCore/qobject.h>
#include <QtCore/qpointer.h>
#include <QtCore/qset.h>
#include <QtCore/qthread.h>
+#include <QtCore/qfuture.h>
#include <QtCore/qurl.h>
#include <QtCore/private/qglobal_p.h>
#include <QtMultimedia/qaudioformat.h>
+#include <QtMultimedia/private/qmaybe_p.h>
+
+#include <memory>
QT_BEGIN_NAMESPACE
@@ -101,6 +104,8 @@ public:
~QSampleCache() override;
QSample* requestSample(const QUrl& url);
+ QFuture<QMaybe<QSample *, QSample::State>> requestSampleFuture(const QUrl &);
+
void setCapacity(qint64 capacity);
bool isLoading() const;
@@ -118,7 +123,10 @@ private:
std::unique_ptr<QIODevice> createStreamForSample(QSample &sample);
private:
- QMap<QUrl, QSample*> m_samples;
+ using SharedSamplePromise = std::shared_ptr<QPromise<QMaybe<QSample *, QSample::State>>>;
+ std::map<SharedSamplePromise, std::unique_ptr<QObject>> m_pendingPromises;
+
+ std::map<QUrl, QSample *> m_samples;
QSet<QSample*> m_staleSamples;
#if QT_CONFIG(network)
diff --git a/src/multimedia/audio/qsoundeffect.cpp b/src/multimedia/audio/qsoundeffect.cpp
index 186797c6e..91a5d1285 100644
--- a/src/multimedia/audio/qsoundeffect.cpp
+++ b/src/multimedia/audio/qsoundeffect.cpp
@@ -4,11 +4,13 @@
#include "qsoundeffect.h"
#include <QtCore/qloggingcategory.h>
+#include <QtCore/qfuture.h>
#include <QtMultimedia/qaudiobuffer.h>
#include <QtMultimedia/qaudiodevice.h>
#include <QtMultimedia/qaudiosink.h>
#include <QtMultimedia/qmediadevices.h>
#include <QtMultimedia/private/qaudiosystem_p.h>
+#include <QtMultimedia/private/qmaybe_p.h>
#include <QtMultimedia/private/qplatformaudiodevices_p.h>
#include <QtMultimedia/private/qplatformaudioresampler_p.h>
#include <QtMultimedia/private/qplatformmediaintegration_p.h>
@@ -72,13 +74,16 @@ public:
void setPlaying(bool playing);
bool updateAudioOutput();
-public Q_SLOTS:
+ void decoderError();
void sampleReady(QSample *);
- void decoderError(QSample *);
+
+public Q_SLOTS:
void stateChanged(QAudio::State);
public:
QSoundEffect *q_ptr;
+ QFuture<void> m_sampleLoadFuture;
+
QUrl m_url;
int m_loopCount = 1;
int m_runningCount = 0;
@@ -104,16 +109,15 @@ QSoundEffectPrivate::QSoundEffectPrivate(QSoundEffect *q, const QAudioDevice &au
void QSoundEffectPrivate::sampleReady(QSample *sample)
{
- if (sample && sample != m_sample.get())
- return;
-
if (m_status == QSoundEffect::Error)
return;
- qCDebug(qLcSoundEffect) << this << "sampleReady: sample size:" << m_sample->data().size();
- disconnect(m_sample.get(), &QSample::error, this, &QSoundEffectPrivate::decoderError);
- disconnect(m_sample.get(), &QSample::ready, this, &QSoundEffectPrivate::sampleReady);
+ Q_ASSERT(sample);
+ Q_ASSERT(sample != m_sample.get());
+
+ m_sample.reset(sample);
+ qCDebug(qLcSoundEffect) << this << "sampleReady: sample size:" << m_sample->data().size();
if (!m_audioSink) {
if (!updateAudioOutput()) // Create audio sink
return; // Returns if no audio devices are available
@@ -128,18 +132,6 @@ void QSoundEffectPrivate::sampleReady(QSample *sample)
}
}
-void QSoundEffectPrivate::decoderError(QSample *sample)
-{
- if (sample && sample != m_sample.get())
- return;
-
- qWarning("QSoundEffect(qaudio): Error decoding source %ls", qUtf16Printable(m_url.toString()));
- disconnect(m_sample.get(), &QSample::ready, this, &QSoundEffectPrivate::sampleReady);
- disconnect(m_sample.get(), &QSample::error, this, &QSoundEffectPrivate::decoderError);
- m_playing = false;
- setStatus(QSoundEffect::Error);
-}
-
void QSoundEffectPrivate::stateChanged(QAudio::State state)
{
qCDebug(qLcSoundEffect) << this << "stateChanged " << state;
@@ -290,6 +282,14 @@ void QSoundEffectPrivate::setPlaying(bool playing)
emit q_ptr->playingChanged();
}
+void QSoundEffectPrivate::decoderError()
+{
+ qWarning("QSoundEffect(qaudio): Error decoding source %ls", qUtf16Printable(m_url.toString()));
+
+ m_playing = false;
+ setStatus(QSoundEffect::Error);
+}
+
/*!
\class QSoundEffect
\brief The QSoundEffect class provides a way to play low latency sound effects.
@@ -432,6 +432,9 @@ void QSoundEffect::setSource(const QUrl &url)
stop();
+ if (d->m_sampleLoadFuture.isValid())
+ d->m_sampleLoadFuture.cancel();
+
d->m_url = url;
d->m_sampleReady = false;
@@ -445,34 +448,22 @@ void QSoundEffect::setSource(const QUrl &url)
return;
}
- if (d->m_sample) {
- if (!d->m_sampleReady) {
- disconnect(d->m_sample.get(), &QSample::error, d, &QSoundEffectPrivate::decoderError);
- disconnect(d->m_sample.get(), &QSample::ready, d, &QSoundEffectPrivate::sampleReady);
- }
- d->m_sample.reset();
- }
+ d->setStatus(QSoundEffect::Loading);
+ d->m_sample = {};
if (d->m_audioSink) {
- disconnect(d->m_audioSink.get(), &QAudioSink::stateChanged, d, &QSoundEffectPrivate::stateChanged);
+ disconnect(d->m_audioSink.get(), &QAudioSink::stateChanged, d,
+ &QSoundEffectPrivate::stateChanged);
d->m_audioSink.reset();
}
- d->setStatus(QSoundEffect::Loading);
- d->m_sample.reset(sampleCache()->requestSample(url));
- connect(d->m_sample.get(), &QSample::error, d, &QSoundEffectPrivate::decoderError);
- connect(d->m_sample.get(), &QSample::ready, d, &QSoundEffectPrivate::sampleReady);
-
- switch (d->m_sample->state()) {
- case QSample::Ready:
- d->sampleReady(d->m_sample.get());
- break;
- case QSample::Error:
- d->decoderError(d->m_sample.get());
- break;
- default:
- break;
- }
+ d->m_sampleLoadFuture = sampleCache()->requestSampleFuture(url).then(
+ this, [this](QMaybe<QSample *, QSample::State> result) {
+ if (result)
+ d->sampleReady(result.value());
+ else
+ d->decoderError();
+ });
emit sourceChanged();
}