6#include <QtConcurrent/qtconcurrentrun.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>
15# include <QtNetwork/qnetworkaccessmanager.h>
16# include <QtNetwork/qnetworkreply.h>
17# include <QtNetwork/qnetworkrequest.h>
32QSampleCache::QSampleCache(QObject *parent)
37 static constexpr int loaderThreadLimit = 8;
38 m_threadPool.setMaxThreadCount(loaderThreadLimit);
39 m_threadPool.setExpiryTimeout(15);
40 m_threadPool.setThreadPriority(QThread::LowPriority);
41 m_threadPool.setServiceLevel(QThread::QualityOfService::Eco);
43 if (!thread()->isMainThread()) {
44 this->moveToThread(qApp->thread());
45 m_threadPool.moveToThread(qApp->thread());
50QSampleCache::~QSampleCache()
54 m_threadPool.waitForDone();
57 for (
auto &entry : m_loadedSamples) {
58 auto samplePtr = entry.second.lock();
60 samplePtr->clearParent();
63 for (
auto &entry : m_pendingSamples) {
64 auto samplePtr = entry.second.first;
66 samplePtr->clearParent();
70QSampleCache::SampleLoadResult QSampleCache::loadSample(QByteArray data)
72 using namespace QtPrivate;
75 bool success = drwav_init_memory(&wavParser, data.constData(), data.size(),
nullptr);
81 QAudioFormat audioFormat;
82 audioFormat.setChannelCount(wavParser.channels);
83 audioFormat.setSampleFormat(QAudioFormat::Float);
84 audioFormat.setSampleRate(wavParser.sampleRate);
85 audioFormat.setChannelConfig(
86 QAudioFormat::defaultChannelConfigForChannelCount(wavParser.channels));
88 QByteArray sampleData;
89 sampleData.resizeForOverwrite(
sizeof(
float) * wavParser.channels
90 * wavParser.totalPCMFrameCount);
91 uint64_t framesRead = drwav_read_pcm_frames_f32(&wavParser, wavParser.totalPCMFrameCount,
92 reinterpret_cast<
float *>(sampleData.data()));
94 if (framesRead != wavParser.totalPCMFrameCount)
98 std::move(sampleData),
103#if QT_CONFIG(network)
107Q_CONSTINIT
thread_local std::optional<QNetworkAccessManager> g_networkAccessManager;
108QNetworkAccessManager &threadLocalNetworkAccessManager()
110 if (!g_networkAccessManager.has_value()) {
111 g_networkAccessManager.emplace();
113 if (QThread::isMainThread()) {
116 g_networkAccessManager.reset();
121 return *g_networkAccessManager;
130QSampleCache::SampleLoadResult
131QSampleCache::loadSample(
const QUrl &url, std::optional<SampleSourceType> forceSourceType)
133 using namespace Qt::Literals;
135 bool errorOccurred =
false;
137 if (url.scheme().isEmpty())
143 std::unique_ptr<QIODevice> decoderInput;
144 SampleSourceType realSourceType =
145 forceSourceType.value_or(url.scheme() == u"qrc"_s || url.scheme() == u"file"_s
146 ? SampleSourceType::File
147 : SampleSourceType::NetworkManager);
148 if (realSourceType == SampleSourceType::File) {
149 QString locationString =
150 url.isLocalFile() ? url.toLocalFile() : u":" + url.toString(QUrl::RemoveScheme);
152 auto *file =
new QFile(locationString);
153 bool opened = file->open(QFile::ReadOnly);
155 errorOccurred =
true;
156 decoderInput.reset(file);
158#if QT_CONFIG(network)
159 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
161 if (reply->error() != QNetworkReply::NoError)
162 errorOccurred =
true;
164 connect(reply, &QNetworkReply::errorOccurred, reply,
165 [&]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
166 errorOccurred =
true;
169 decoderInput.reset(reply);
175 if (!decoderInput->isOpen())
178 QByteArray data = decoderInput->readAll();
179 if (data.isEmpty() || errorOccurred)
182 return loadSample(std::move(data));
187QFuture<QSampleCache::SampleLoadResult> QSampleCache::loadSampleAsync(
const QUrl &url)
189 auto promise = std::make_shared<QPromise<QSampleCache::SampleLoadResult>>();
190 auto future = promise->future();
192 auto fulfilPromise = [&](
auto &&result)
mutable {
194 promise->addResult(result);
198 using namespace Qt::Literals;
200 SampleSourceType realSourceType = (url.scheme() == u"qrc"_s || url.scheme() == u"file"_s)
201 ? SampleSourceType::File
202 : SampleSourceType::NetworkManager;
203 if (realSourceType == SampleSourceType::File) {
204 QString locationString = url.toString(QUrl::RemoveScheme);
205 if (url.scheme() == u"qrc"_s)
206 locationString = u":" + locationString;
207 QFile file{ locationString };
208 bool opened = file.open(QFile::ReadOnly);
210 fulfilPromise(std::nullopt);
214 QByteArray data = file.readAll();
215 if (data.isEmpty()) {
216 fulfilPromise(std::nullopt);
220 fulfilPromise(loadSample(std::move(data)));
224#if QT_CONFIG(network)
226 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
228 if (reply->error() != QNetworkReply::NoError) {
229 fulfilPromise(std::nullopt);
234 connect(reply, &QNetworkReply::errorOccurred, reply,
235 [reply, promise]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
237 promise->addResult(std::nullopt);
239 reply->deleteLater();
242 connect(reply, &QNetworkReply::finished, reply, [promise, reply] {
244 QByteArray data = reply->readAll();
246 promise->addResult(std::nullopt);
248 promise->addResult(loadSample(std::move(data)));
250 reply->deleteLater();
253 fulfilPromise(std::nullopt);
258bool QSampleCache::isCached(
const QUrl &url)
const
260 std::lock_guard guard(m_mutex);
262 return m_loadedSamples.find(url) != m_loadedSamples.end()
263 || m_pendingSamples.find(url) != m_pendingSamples.end();
266QFuture<SharedSamplePtr> QSampleCache::requestSampleFuture(
const QUrl &url)
268 std::lock_guard guard(m_mutex);
270 auto promise = std::make_shared<QPromise<SharedSamplePtr>>();
271 auto future = promise->future();
274 auto found = m_loadedSamples.find(url);
275 if (found != m_loadedSamples.end()) {
276 SharedSamplePtr foundSample = found->second.lock();
277 Q_ASSERT(foundSample);
278 Q_ASSERT(foundSample->state() == QSample::Ready);
280 promise->addResult(std::move(foundSample));
286 auto pending = m_pendingSamples.find(url);
287 if (pending != m_pendingSamples.end()) {
288 pending->second.second.append(promise);
293 SharedSamplePtr sample = std::make_shared<QSample>(url,
this);
294 m_pendingSamples.emplace(url, std::pair{ sample, QList<SharedSamplePromise>{ promise } });
297 QFuture<SampleLoadResult> futureResult =
298 QtConcurrent::run(&m_threadPool, [url, type = m_sampleSourceType] {
299 return loadSample(url, type);
302 QFuture<SampleLoadResult> futureResult = loadSampleAsync(url);
305 futureResult.then(
this,
306 [
this, url, sample = std::move(sample)](SampleLoadResult loadResult)
mutable {
308 sample->setData(loadResult->first, loadResult->second);
312 std::lock_guard guard(m_mutex);
314 auto pending = m_pendingSamples.find(url);
315 if (pending != m_pendingSamples.end()) {
316 for (
auto &promise : pending->second.second) {
318 promise->addResult(loadResult ? sample :
nullptr);
324 m_loadedSamples.emplace(url, sample);
326 if (pending != m_pendingSamples.end())
327 m_pendingSamples.erase(pending);
338 m_parent->removeUnreferencedSample(m_url);
340 qCDebug(qLcSampleCache) <<
"~QSample" <<
this <<
": deleted [" << m_url <<
"]" << QThread::currentThread();
343void QSampleCache::removeUnreferencedSample(
const QUrl &url)
345 std::lock_guard guard(m_mutex);
346 m_loadedSamples.erase(url);
349void QSample::setError()
351 m_state = State::Error;
354void QSample::setData(QByteArray data, QAudioFormat format)
356 m_state = State::Ready;
357 m_soundData = std::move(data);
358 m_audioFormat = format;
361QSample::State QSample::state()
const
366QSample::QSample(QUrl url, QSampleCache *parent) : m_parent(parent), m_url(std::move(url)) { }
368void QSample::clearParent()
375#if !QT_CONFIG(thread)
Q_STATIC_LOGGING_CATEGORY(lcAccessibilityCore, "qt.accessibility.core")