diff options
author | Tim Blechmann <[email protected]> | 2025-03-28 16:02:45 +0800 |
---|---|---|
committer | Tim Blechmann <[email protected]> | 2025-06-05 07:57:42 +0800 |
commit | 45d3cd3ef0747194921ec96ea1c9cefc9ca04da6 (patch) | |
tree | 01e3c1dd3758861108a1c23983551a7be2d77744 | |
parent | 10b93774245065453696adb56fcc9bdf734593a0 (diff) |
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.cpp | 68 | ||||
-rw-r--r-- | src/multimedia/audio/qsamplecache_p.h | 12 | ||||
-rw-r--r-- | src/multimedia/audio/qsoundeffect.cpp | 77 |
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(); } |