6#include <QtConcurrent/qtconcurrentrun.h>
7#include <QtCore/qapplicationstatic.h>
8#include <QtCore/qcoreapplication.h>
9#include <QtCore/qdebug.h>
10#include <QtCore/qeventloop.h>
11#include <QtCore/qfile.h>
12#include <QtCore/qfuturewatcher.h>
13#include <QtCore/qloggingcategory.h>
16# include <QtNetwork/qnetworkaccessmanager.h>
17# include <QtNetwork/qnetworkreply.h>
18# include <QtNetwork/qnetworkrequest.h>
33Q_APPLICATION_STATIC(QSampleCache, sampleCache)
35QSampleCache *QSampleCache::instance()
40QSampleCache::QSampleCache(QObject *parent) : QObject(parent)
44 static constexpr int loaderThreadLimit = 8;
45 m_threadPool.setObjectName(
"QSampleCachePool");
46 m_threadPool.setMaxThreadCount(loaderThreadLimit);
47 m_threadPool.setExpiryTimeout(15);
48 m_threadPool.setThreadPriority(QThread::LowPriority);
49 m_threadPool.setServiceLevel(QThread::QualityOfService::Eco);
51 if (!thread()->isMainThread()) {
52 this->moveToThread(qApp->thread());
53 m_threadPool.moveToThread(qApp->thread());
59 Q_ASSERT(qApp &&
"QApplication is still valid");
61 QSampleCache *instance = sampleCache();
63 instance->m_threadPool.clear();
64 instance->m_threadPool.waitForDone();
70QSampleCache::~QSampleCache()
74 m_threadPool.waitForDone();
77 for (
auto &entry : m_loadedSamples) {
78 auto samplePtr = entry.second.lock();
80 samplePtr->clearParent();
83 for (
auto &entry : m_pendingSamples) {
84 auto samplePtr = entry.second.first;
86 samplePtr->clearParent();
90QSampleCache::SampleLoadResult QSampleCache::loadSample(QByteArray data)
92 using namespace QtPrivate;
95 bool success = drwav_init_memory(&wavParser, data.constData(), data.size(),
nullptr);
101 QAudioFormat audioFormat;
102 audioFormat.setChannelCount(wavParser.channels);
103 audioFormat.setSampleFormat(QAudioFormat::Float);
104 audioFormat.setSampleRate(wavParser.sampleRate);
105 audioFormat.setChannelConfig(
106 QAudioFormat::defaultChannelConfigForChannelCount(wavParser.channels));
108 QByteArray sampleData;
109 sampleData.resizeForOverwrite(
sizeof(
float) * wavParser.channels
110 * wavParser.totalPCMFrameCount);
111 uint64_t framesRead = drwav_read_pcm_frames_f32(&wavParser, wavParser.totalPCMFrameCount,
112 reinterpret_cast<
float *>(sampleData.data()));
114 if (framesRead != wavParser.totalPCMFrameCount)
118 std::move(sampleData),
123#if QT_CONFIG(network)
127Q_CONSTINIT
thread_local std::optional<QNetworkAccessManager> g_networkAccessManager;
128QNetworkAccessManager &threadLocalNetworkAccessManager()
130 if (!g_networkAccessManager.has_value()) {
131 g_networkAccessManager.emplace();
133 if (QThread::isMainThread()) {
136 g_networkAccessManager.reset();
141 return *g_networkAccessManager;
150QSampleCache::SampleLoadResult
151QSampleCache::loadSample(
const QUrl &url, std::optional<SampleSourceType> forceSourceType)
153 using namespace Qt::Literals;
155 bool errorOccurred =
false;
157 if (url.scheme().isEmpty())
163 std::unique_ptr<QIODevice> decoderInput;
164 SampleSourceType realSourceType =
165 forceSourceType.value_or(url.scheme() == u"qrc"_s || url.scheme() == u"file"_s
166 ? SampleSourceType::File
167 : SampleSourceType::NetworkManager);
168 if (realSourceType == SampleSourceType::File) {
169 QString locationString =
170 url.isLocalFile() ? url.toLocalFile() : u":" + url.toString(QUrl::RemoveScheme);
172 auto *file =
new QFile(locationString);
173 bool opened = file->open(QFile::ReadOnly);
175 errorOccurred =
true;
176 decoderInput.reset(file);
178#if QT_CONFIG(network)
179 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
181 if (reply->error() != QNetworkReply::NoError)
182 errorOccurred =
true;
184 connect(reply, &QNetworkReply::errorOccurred, reply,
185 [&]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
186 errorOccurred =
true;
189 decoderInput.reset(reply);
195 if (!decoderInput->isOpen())
198 QByteArray data = decoderInput->readAll();
199 if (data.isEmpty() || errorOccurred)
202 return loadSample(std::move(data));
207QFuture<QSampleCache::SampleLoadResult> QSampleCache::loadSampleAsync(
const QUrl &url)
209 auto promise = std::make_shared<QPromise<QSampleCache::SampleLoadResult>>();
210 auto future = promise->future();
212 auto fulfilPromise = [&](
auto &&result)
mutable {
214 promise->addResult(result);
218 using namespace Qt::Literals;
220 SampleSourceType realSourceType = (url.scheme() == u"qrc"_s || url.scheme() == u"file"_s)
221 ? SampleSourceType::File
222 : SampleSourceType::NetworkManager;
223 if (realSourceType == SampleSourceType::File) {
224 QString locationString = url.toString(QUrl::RemoveScheme);
225 if (url.scheme() == u"qrc"_s)
226 locationString = u":" + locationString;
227 QFile file{ locationString };
228 bool opened = file.open(QFile::ReadOnly);
230 fulfilPromise(std::nullopt);
234 QByteArray data = file.readAll();
235 if (data.isEmpty()) {
236 fulfilPromise(std::nullopt);
240 fulfilPromise(loadSample(std::move(data)));
244#if QT_CONFIG(network)
246 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
248 if (reply->error() != QNetworkReply::NoError) {
249 fulfilPromise(std::nullopt);
254 connect(reply, &QNetworkReply::errorOccurred, reply,
255 [reply, promise]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
257 promise->addResult(std::nullopt);
259 reply->deleteLater();
262 connect(reply, &QNetworkReply::finished, reply, [promise, reply] {
264 QByteArray data = reply->readAll();
266 promise->addResult(std::nullopt);
268 promise->addResult(loadSample(std::move(data)));
270 reply->deleteLater();
273 fulfilPromise(std::nullopt);
278bool QSampleCache::isCached(
const QUrl &url)
const
280 std::lock_guard guard(m_mutex);
282 return m_loadedSamples.find(url) != m_loadedSamples.end()
283 || m_pendingSamples.find(url) != m_pendingSamples.end();
286QFuture<SharedSamplePtr> QSampleCache::requestSampleFuture(
const QUrl &url)
288 std::lock_guard guard(m_mutex);
290 auto promise = std::make_shared<QPromise<SharedSamplePtr>>();
291 auto future = promise->future();
294 auto found = m_loadedSamples.find(url);
295 if (found != m_loadedSamples.end()) {
296 SharedSamplePtr foundSample = found->second.lock();
297 Q_ASSERT(foundSample);
298 Q_ASSERT(foundSample->state() == QSample::Ready);
300 promise->addResult(std::move(foundSample));
306 auto pending = m_pendingSamples.find(url);
307 if (pending != m_pendingSamples.end()) {
308 pending->second.second.append(promise);
313 SharedSamplePtr sample = std::make_shared<QSample>(url,
this);
314 m_pendingSamples.emplace(url, std::pair{ sample, QList<SharedSamplePromise>{ promise } });
317 QFuture<SampleLoadResult> futureResult =
318 QtConcurrent::run(&m_threadPool, [url, type = m_sampleSourceType] {
319 return loadSample(url, type);
322 QFuture<SampleLoadResult> futureResult = loadSampleAsync(url);
325 futureResult.then(
this,
326 [
this, url, sample = std::move(sample)](SampleLoadResult loadResult)
mutable {
328 sample->setData(loadResult->first, loadResult->second);
332 std::lock_guard guard(m_mutex);
334 auto pending = m_pendingSamples.find(url);
335 if (pending != m_pendingSamples.end()) {
336 for (
auto &promise : pending->second.second) {
338 promise->addResult(loadResult ? sample :
nullptr);
344 m_loadedSamples.emplace(url, sample);
346 if (pending != m_pendingSamples.end())
347 m_pendingSamples.erase(pending);
358 m_parent->removeUnreferencedSample(m_url);
360 qCDebug(qLcSampleCache) <<
"~QSample" <<
this <<
": deleted [" << m_url <<
"]" << QThread::currentThread();
363void QSampleCache::removeUnreferencedSample(
const QUrl &url)
365 std::lock_guard guard(m_mutex);
366 m_loadedSamples.erase(url);
369void QSample::setError()
371 m_state = State::Error;
374void QSample::setData(QByteArray data, QAudioFormat format)
376 m_state = State::Ready;
377 m_soundData = std::move(data);
378 m_audioFormat = format;
381QSample::State QSample::state()
const
386QSample::QSample(QUrl url, QSampleCache *parent) : m_parent(parent), m_url(std::move(url)) { }
388void QSample::clearParent()
395#if !QT_CONFIG(thread)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)