6#include <QtCore/qapplicationstatic.h>
7#include <QtCore/qcoreapplication.h>
8#include <QtCore/qdebug.h>
9#include <QtCore/qeventloop.h>
10#include <QtCore/qfile.h>
11#include <QtCore/qfuturewatcher.h>
12#include <QtCore/qloggingcategory.h>
13#include <QtConcurrent/qtconcurrentrun.h>
16# include <QtNetwork/qnetworkaccessmanager.h>
17# include <QtNetwork/qnetworkreply.h>
18# include <QtNetwork/qnetworkrequest.h>
33Q_APPLICATION_STATIC(QSampleCache, sampleCache)
35QSampleCache *QSampleCache::instance()
41QThreadPool *QSampleCache::threadPool()
44 return QThreadPool::globalInstance();
51QSampleCache::QSampleCache(QObject *parent) : QObject(parent)
54 if (!thread()->isMainThread())
55 moveToThread(qApp->thread());
57# if !defined(Q_OS_WASM)
59 static constexpr int loaderThreadLimit = 8;
60 m_threadPool.setObjectName(
"QSampleCachePool");
61 m_threadPool.setMaxThreadCount(loaderThreadLimit);
62 m_threadPool.setExpiryTimeout(15);
63 m_threadPool.setThreadPriority(QThread::LowPriority);
64 m_threadPool.setServiceLevel(QThread::QualityOfService::Eco);
69 Q_ASSERT(qApp &&
"QApplication is still valid");
71 QSampleCache *instance = sampleCache();
73 instance->m_threadPool.clear();
74 instance->m_threadPool.waitForDone();
81QSampleCache::~QSampleCache()
83#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
85 m_threadPool.waitForDone();
88 for (
auto &entry : m_loadedSamples) {
89 auto samplePtr = entry.second.lock();
91 samplePtr->clearParent();
94 for (
auto &entry : m_pendingSamples) {
95 auto samplePtr = entry.second.first;
97 samplePtr->clearParent();
101QSampleCache::SampleLoadResult QSampleCache::loadSample(QSpan<
const char> data)
103 using namespace QtPrivate;
106 bool success = drwav_init_memory(&wavParser, data.data(), data.size(),
nullptr);
112 QAudioFormat audioFormat;
113 audioFormat.setChannelCount(wavParser.channels);
114 audioFormat.setSampleFormat(QAudioFormat::Float);
115 audioFormat.setSampleRate(wavParser.sampleRate);
116 audioFormat.setChannelConfig(
117 QAudioFormat::defaultChannelConfigForChannelCount(wavParser.channels));
119 QByteArray sampleData;
120 sampleData.resizeForOverwrite(
sizeof(
float) * wavParser.channels
121 * wavParser.totalPCMFrameCount);
122 uint64_t framesRead = drwav_read_pcm_frames_f32(&wavParser, wavParser.totalPCMFrameCount,
123 reinterpret_cast<
float *>(sampleData.data()));
125 if (framesRead != wavParser.totalPCMFrameCount)
129 std::move(sampleData),
134#if QT_CONFIG(network)
138Q_CONSTINIT
thread_local std::optional<QNetworkAccessManager> g_networkAccessManager;
139QNetworkAccessManager &threadLocalNetworkAccessManager()
141 if (!g_networkAccessManager.has_value()) {
142 g_networkAccessManager.emplace();
144 if (QThread::isMainThread()) {
147 g_networkAccessManager.reset();
152 return *g_networkAccessManager;
161QSampleCache::SampleLoadResult
162QSampleCache::loadSample(
const QUrl &url, std::optional<SampleSourceType> forceSourceType)
164 using namespace Qt::Literals;
166 bool errorOccurred =
false;
168 if (url.scheme().isEmpty())
174 std::unique_ptr<QIODevice> decoderInput;
175 SampleSourceType realSourceType =
176 forceSourceType.value_or(url.scheme() == u"qrc"_s || url.scheme() == u"file"_s
177 ? SampleSourceType::File
178 : SampleSourceType::NetworkManager);
179 if (realSourceType == SampleSourceType::File) {
180 QString locationString =
181 url.isLocalFile() ? url.toLocalFile() : u":" + url.toString(QUrl::RemoveScheme);
183 auto *file =
new QFile(locationString);
184 bool opened = file->open(QFile::ReadOnly);
186 errorOccurred =
true;
187 decoderInput.reset(file);
189#if QT_CONFIG(network)
190 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
192 if (reply->error() != QNetworkReply::NoError)
193 errorOccurred =
true;
195 connect(reply, &QNetworkReply::errorOccurred, reply,
196 [&]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
197 errorOccurred =
true;
200 decoderInput.reset(reply);
206 if (!decoderInput->isOpen())
209 QByteArray data = decoderInput->readAll();
210 if (data.isEmpty() || errorOccurred)
213 return loadSample(data);
218QFuture<QSampleCache::SampleLoadResult> QSampleCache::loadSampleAsync(
const QUrl &url)
220 auto promise = std::make_shared<QPromise<QSampleCache::SampleLoadResult>>();
221 auto future = promise->future();
223 auto fulfilPromise = [&](
auto &&result)
mutable {
225 promise->addResult(result);
229 using namespace Qt::Literals;
231 SampleSourceType realSourceType = (url.scheme() == u"qrc"_s || url.scheme() == u"file"_s)
232 ? SampleSourceType::File
233 : SampleSourceType::NetworkManager;
234 if (realSourceType == SampleSourceType::File) {
235 QString locationString = url.toString(QUrl::RemoveScheme);
236 if (url.scheme() == u"qrc"_s)
237 locationString = u":" + locationString;
238 QFile file{ locationString };
239 bool opened = file.open(QFile::ReadOnly);
241 fulfilPromise(std::nullopt);
245 QByteArray data = file.readAll();
246 if (data.isEmpty()) {
247 fulfilPromise(std::nullopt);
251 fulfilPromise(loadSample(data));
255#if QT_CONFIG(network)
257 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
259 if (reply->error() != QNetworkReply::NoError) {
260 fulfilPromise(std::nullopt);
261 reply->deleteLater();
265 connect(reply, &QNetworkReply::errorOccurred, reply,
266 [reply, promise]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
268 promise->addResult(std::nullopt);
270 reply->deleteLater();
273 connect(reply, &QNetworkReply::finished, reply, [promise, reply] {
275 QByteArray data = reply->readAll();
277 promise->addResult(std::nullopt);
279 promise->addResult(loadSample(data));
281 reply->deleteLater();
284 fulfilPromise(std::nullopt);
289bool QSampleCache::isCached(
const QUrl &url)
const
291 std::lock_guard guard(m_mutex);
293 return m_loadedSamples.find(url) != m_loadedSamples.end()
294 || m_pendingSamples.find(url) != m_pendingSamples.end();
297QFuture<SharedSamplePtr> QSampleCache::requestSampleFuture(
const QUrl &url)
299 std::lock_guard guard(m_mutex);
301 auto promise = std::make_shared<QPromise<SharedSamplePtr>>();
302 auto future = promise->future();
305 auto found = m_loadedSamples.find(url);
306 if (found != m_loadedSamples.end()) {
307 SharedSamplePtr foundSample = found->second.lock();
308 Q_ASSERT(foundSample);
309 Q_ASSERT(foundSample->state() == QSample::Ready);
311 promise->addResult(std::move(foundSample));
317 auto pending = m_pendingSamples.find(url);
318 if (pending != m_pendingSamples.end()) {
319 pending->second.second.append(promise);
324 SharedSamplePtr sample = std::make_shared<QSample>(url,
this);
325 m_pendingSamples.emplace(url, std::pair{ sample, QList<SharedSamplePromise>{ promise } });
327 QFuture<SampleLoadResult> futureResult = [&] {
329 if (threadPool()->maxThreadCount() > 0)
330 return QtConcurrent::run(threadPool(), [url, type = m_sampleSourceType] {
331 return loadSample(url, type);
334 return loadSampleAsync(url);
337 futureResult.then(
this,
338 [
this, url, sample = std::move(sample)](SampleLoadResult loadResult)
mutable {
340 sample->setData(loadResult->first, loadResult->second);
344 std::lock_guard guard(m_mutex);
346 auto pending = m_pendingSamples.find(url);
347 if (pending != m_pendingSamples.end()) {
348 for (
auto &promise : pending->second.second) {
350 promise->addResult(loadResult ? sample :
nullptr);
356 m_loadedSamples.emplace(url, sample);
358 if (pending != m_pendingSamples.end())
359 m_pendingSamples.erase(pending);
370 m_parent->removeUnreferencedSample(m_url);
372 qCDebug(qLcSampleCache) <<
"~QSample" <<
this <<
": deleted [" << m_url <<
"]" << QThread::currentThread();
375void QSampleCache::removeUnreferencedSample(
const QUrl &url)
377 std::lock_guard guard(m_mutex);
378 m_loadedSamples.erase(url);
381void QSample::setError()
383 m_state = State::Error;
386void QSample::setData(QByteArray data, QAudioFormat format)
388 m_state = State::Ready;
389 m_soundData = std::move(data);
390 m_audioFormat = format;
393QSample::State QSample::state()
const
398QSample::QSample(QUrl url, QSampleCache *parent) : m_parent(parent), m_url(std::move(url)) { }
400void QSample::clearParent()
407#if !QT_CONFIG(thread)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)