7#include <QtCore/qapplicationstatic.h>
8#include <QtCore/qbuffer.h>
9#include <QtCore/qcoreapplication.h>
10#include <QtCore/qdebug.h>
11#include <QtCore/qeventloop.h>
12#include <QtCore/qfile.h>
13#include <QtCore/qfuturewatcher.h>
14#include <QtCore/qloggingcategory.h>
15#include <QtMultimedia/qaudiobuffer.h>
16#include <QtMultimedia/qaudiodecoder.h>
17#include <QtMultimedia/private/qmultimediautils_p.h>
18#include <QtConcurrent/qtconcurrentrun.h>
21# include <QtNetwork/qnetworkaccessmanager.h>
22# include <QtNetwork/qnetworkreply.h>
23# include <QtNetwork/qnetworkrequest.h>
38QSample::QSample(QUrl url, QSampleCache *parent) : m_parent(parent), m_url(std::move(url)) { }
40QSample::QSample(QUrl url, QSampleCache *parent, std::optional<SampleRate> targetSampleRate)
41 : m_parent(parent), m_url(std::move(url)), m_targetSampleRate(targetSampleRate)
49 m_parent->removeUnreferencedSample(m_url, m_targetSampleRate);
51 qCDebug(qLcSampleCache) <<
"~QSample" <<
this <<
": deleted [" << m_url <<
"]" << QThread::currentThread();
54void QSample::setError()
56 m_state = State::Error;
59void QSample::setData(QByteArray data, QAudioFormat format)
61 m_state = State::Ready;
62 m_soundData = std::move(data);
63 m_audioFormat = format;
66QSample::State QSample::state()
const
71void QSample::clearParent()
78Q_APPLICATION_STATIC(QSampleCache, sampleCache)
80QSampleCache *QSampleCache::instance()
86QThreadPool *QSampleCache::threadPool()
89 return QThreadPool::globalInstance();
96QSampleCache::QSampleCache(QObject *parent) : QObject(parent)
99 if (!thread()->isMainThread())
100 moveToThread(qApp->thread());
102# if !defined(Q_OS_WASM)
104 static constexpr int loaderThreadLimit = 8;
105 m_threadPool.setObjectName(
"QSampleCachePool");
106 m_threadPool.setMaxThreadCount(loaderThreadLimit);
107 m_threadPool.setExpiryTimeout(15);
108 m_threadPool.setThreadPriority(QThread::LowPriority);
109 m_threadPool.setServiceLevel(QThread::QualityOfService::Eco);
114 Q_ASSERT(qApp &&
"QApplication is still valid");
116 QSampleCache *instance = sampleCache();
118 instance->m_threadPool.clear();
119 instance->m_threadPool.waitForDone();
126QSampleCache::~QSampleCache()
128#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
129 m_threadPool.clear();
130 m_threadPool.waitForDone();
133 for (
auto &entry : m_loadedSamples) {
134 auto samplePtr = entry.second.lock();
136 samplePtr->clearParent();
139 for (
auto &entry : m_pendingSamples) {
140 auto samplePtr = entry.second.first;
142 samplePtr->clearParent();
146QSampleCache::SampleLoadResult QSampleCache::loadSample(QSpan<
const char> data)
148 using namespace QtPrivate;
151 bool success = drwav_init_memory(&wavParser, data.data(), data.size(),
nullptr);
153 return q23::unexpected(QSampleLoadError::FormatError);
155 auto cleanup = qScopeGuard([&] {
156 drwav_uninit(&wavParser);
161 QAudioFormat audioFormat;
162 audioFormat.setChannelCount(wavParser.channels);
163 audioFormat.setSampleFormat(QAudioFormat::Float);
164 audioFormat.setSampleRate(wavParser.sampleRate);
165 audioFormat.setChannelConfig(
166 QAudioFormat::defaultChannelConfigForChannelCount(wavParser.channels));
168 QByteArray sampleData;
169 sampleData.resizeForOverwrite(qsizetype(
sizeof(
float) * wavParser.channels
170 * wavParser.totalPCMFrameCount));
171 uint64_t framesRead = drwav_read_pcm_frames_f32(&wavParser, wavParser.totalPCMFrameCount,
172 reinterpret_cast<
float *>(sampleData.data()));
174 if (framesRead != wavParser.totalPCMFrameCount)
175 return q23::unexpected(QSampleLoadError::FormatError);
178 std::move(sampleData),
185QByteArray convertToFloat32(
const QByteArray &data,
const QAudioFormat &fmt)
187 if (fmt.sampleFormat() == QAudioFormat::Float)
190 int totalSamples = fmt.framesForBytes(data.size()) * fmt.channelCount();
193 totalSamples *
int(
sizeof(
float)),
194 Qt::Initialization::Uninitialized,
198 convertSampleFormat(as_bytes(QSpan{ data }), toNativeSampleFormat(fmt.sampleFormat()),
204QSampleCache::SampleLoadResult runDecoderLoop(QAudioDecoder &decoder, QEventLoop &loop)
206 using SampleLoadResult = QSampleCache::SampleLoadResult;
208 QByteArray accumulated;
210 SampleLoadResult result = q23::unexpected(QSampleLoadError::DecoderError);
212 QObject::connect(&decoder, &QAudioDecoder::bufferReady, &loop, [&] {
213 QAudioBuffer buf = decoder.read();
218 accumulated.append(buf.constData<
char>(), buf.byteCount());
221 QObject::connect(&decoder, &QAudioDecoder::finished, &loop, [&] {
222 QByteArray floatData = convertToFloat32(accumulated, fmt);
223 QAudioFormat floatFmt = fmt;
224 floatFmt.setSampleFormat(QAudioFormat::Float);
225 result = std::pair{ std::move(floatData), floatFmt };
229 QObject::connect(&decoder, qOverload<QAudioDecoder::Error>(&QAudioDecoder::error),
230 &loop, [&](QAudioDecoder::Error) {
231 result = q23::unexpected(QSampleLoadError::DecoderError);
236 if (decoder.error() != QAudioDecoder::NoError)
237 return q23::unexpected(QSampleLoadError::DecoderError);
239 loop.exec(QEventLoop::ExcludeUserInputEvents);
246QSampleCache::SampleLoadResult
247QSampleCache::loadSampleViaDecoder(std::variant<QUrl, QByteArray> arg)
250 using namespace QtMultimediaPrivate;
252 QAudioDecoder decoder;
253 if (!decoder.isSupported())
254 return q23::unexpected(QSampleLoadError::NotSupported);
258 return std::visit(qOverloadedVisitor([&](QUrl url) -> SampleLoadResult {
259 decoder.setSource(url);
260 return runDecoderLoop(decoder, loop);
261 }, [&](QByteArray data) -> SampleLoadResult {
263 buffer.setData(data);
264 if (!buffer.open(QIODevice::ReadOnly))
265 return q23::unexpected(QSampleLoadError::IoError);
266 decoder.setSourceDevice(&buffer);
268 return runDecoderLoop(decoder, loop);
273#if QT_CONFIG(network)
277Q_CONSTINIT
thread_local std::optional<QNetworkAccessManager> g_networkAccessManager;
278QNetworkAccessManager &threadLocalNetworkAccessManager()
280 if (!g_networkAccessManager.has_value()) {
281 g_networkAccessManager.emplace();
283 if (QThread::isMainThread()) {
286 g_networkAccessManager.reset();
291 return *g_networkAccessManager;
300QSampleCache::SampleLoadResult
301QSampleCache::loadSample(
const QUrl &url, std::optional<SampleSourceType> forceSourceType)
303 using namespace Qt::Literals;
305 SampleSourceType realSourceType =
306 forceSourceType.value_or(url.scheme() == u"qrc"_s || url.scheme() == u"file"_s
307 ? SampleSourceType::File
308 : SampleSourceType::NetworkManager);
310 if (realSourceType == SampleSourceType::AudioDecoder)
311 return loadSampleViaDecoder(url);
313 if (url.scheme().isEmpty())
317 return q23::unexpected(QSampleLoadError::IoError);
319 bool errorOccurred =
false;
321 std::unique_ptr<QIODevice> decoderInput;
322 if (realSourceType == SampleSourceType::File) {
323 QString locationString =
324 url.isLocalFile() ? url.toLocalFile() : u":" + url.toString(QUrl::RemoveScheme);
326 auto *file =
new QFile(locationString);
327 bool opened = file->open(QFile::ReadOnly);
329 errorOccurred =
true;
330 decoderInput.reset(file);
332#if QT_CONFIG(network)
333 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
335 if (reply->error() != QNetworkReply::NoError)
336 errorOccurred =
true;
338 connect(reply, &QNetworkReply::errorOccurred, reply,
339 [&]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
340 errorOccurred =
true;
343 decoderInput.reset(reply);
345 return q23::unexpected(QSampleLoadError::IoError);
349 if (!decoderInput->isOpen())
350 return q23::unexpected(QSampleLoadError::IoError);
352 QByteArray data = decoderInput->readAll();
353 if (data.isEmpty() || errorOccurred)
354 return q23::unexpected(QSampleLoadError::IoError);
356 SampleLoadResult result = loadSample(data);
357 if (!result && result.error() == QSampleLoadError::FormatError && !forceSourceType) {
358 qCDebug(qLcSampleCache) <<
"drwav failed, retrying via QAudioDecoder";
359 result = loadSampleViaDecoder(data);
361 qCDebug(qLcSampleCache) <<
"device-based decode failed, retrying via URL";
362 result = loadSampleViaDecoder(url);
370QFuture<QSampleCache::SampleLoadResult>
371QSampleCache::loadSampleAsync(
const QUrl &url, std::optional<SampleSourceType> forceSourceType)
373 auto promise = std::make_shared<QPromise<QSampleCache::SampleLoadResult>>();
374 auto future = promise->future();
376 auto fulfilPromise = [&](
auto &&result)
mutable {
378 promise->addResult(result);
382 using namespace Qt::Literals;
384 SampleSourceType realSourceType = (url.scheme() == u"qrc"_s || url.scheme() == u"file"_s)
385 ? SampleSourceType::File
386 : SampleSourceType::NetworkManager;
387 if (realSourceType == SampleSourceType::File) {
388 QString locationString = url.toString(QUrl::RemoveScheme);
389 if (url.scheme() == u"qrc"_s)
390 locationString = u":" + locationString;
391 QFile file{ locationString };
392 bool opened = file.open(QFile::ReadOnly);
394 fulfilPromise(q23::unexpected(QSampleLoadError::IoError));
398 QByteArray data = file.readAll();
399 if (data.isEmpty()) {
400 fulfilPromise(q23::unexpected(QSampleLoadError::IoError));
404 if (forceSourceType == SampleSourceType::AudioDecoder)
405 return loadSampleAsyncViaDecoder(data);
407 auto drwavResult = loadSample(data);
408 if (drwavResult || drwavResult.error() != QSampleLoadError::FormatError) {
409 fulfilPromise(drwavResult);
412 return loadSampleAsyncViaDecoder(data);
415#if QT_CONFIG(network)
417 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
419 if (reply->error() != QNetworkReply::NoError) {
420 fulfilPromise(q23::unexpected(QSampleLoadError::IoError));
421 reply->deleteLater();
425 connect(reply, &QNetworkReply::errorOccurred, reply,
426 [reply, promise]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
428 promise->addResult(q23::unexpected(QSampleLoadError::IoError));
430 reply->deleteLater();
433 connect(reply, &QNetworkReply::finished, reply,
434 [promise, reply, fulfilPromise = std::move(fulfilPromise), forceSourceType]()
mutable {
435 QByteArray data = reply->readAll();
436 if (data.isEmpty()) {
438 promise->addResult(q23::unexpected(QSampleLoadError::IoError));
440 }
else if (forceSourceType == SampleSourceType::AudioDecoder) {
441 auto decoderFuture = loadSampleAsyncViaDecoder(data);
442 decoderFuture.then([promise](SampleLoadResult result) {
444 promise->addResult(std::move(result));
448 auto drwavResult = loadSample(data);
449 if (drwavResult || drwavResult.error() != QSampleLoadError::FormatError) {
450 fulfilPromise(drwavResult);
452 auto decoderFuture = loadSampleAsyncViaDecoder(data);
453 decoderFuture.then([promise](SampleLoadResult result) {
455 promise->addResult(std::move(result));
460 reply->deleteLater();
463 fulfilPromise(q23::unexpected(QSampleLoadError::IoError));
473struct QStaticDecoderSingleton
475 void addDecoder(std::shared_ptr<QAudioDecoder> decoder)
477 std::lock_guard guard(mutex);
478 decoders.insert(std::move(decoder));
481 void removeDecoder(std::shared_ptr<QAudioDecoder> decoder)
483 std::lock_guard guard(mutex);
484 decoders.erase(decoder);
487 std::set<std::shared_ptr<QAudioDecoder>> decoders;
491Q_APPLICATION_STATIC(QStaticDecoderSingleton, decoders);
495QFuture<QSampleCache::SampleLoadResult>
496QSampleCache::loadSampleAsyncViaDecoder(QByteArray data)
505 auto promise = std::make_shared<QPromise<SampleLoadResult>>();
506 auto future = promise->future();
508 auto decoder = QtMultimediaPrivate::makeSharedDeleteLater<QAudioDecoder>();
509 if (!decoder->isSupported()) {
511 promise->addResult(q23::unexpected(QSampleLoadError::NotSupported));
516 auto *buffer =
new QBuffer(decoder.get());
517 buffer->setData(data);
518 buffer->open(QIODevice::ReadOnly);
520 auto accum = std::make_shared<QByteArray>();
521 auto fmt = std::make_shared<QAudioFormat>();
523 decoders->addDecoder(decoder);
525 QObject::connect(decoder.get(), &QAudioDecoder::bufferReady, decoder.get(),
526 [weakDecoder = std::weak_ptr<QAudioDecoder>(decoder), accum, fmt] {
527 auto decoder = weakDecoder.lock();
531 QAudioBuffer buf = decoder->read();
536 accum->append(buf.constData<
char>(), buf.byteCount());
539 QObject::connect(decoder.get(), &QAudioDecoder::finished, decoder.get(),
540 [promise, accum, fmt, weakDecoder = std::weak_ptr<QAudioDecoder>(decoder)] {
541 auto decoder = weakDecoder.lock();
545 QByteArray floatData = convertToFloat32(*accum, *fmt);
546 QAudioFormat floatFmt = *fmt;
547 floatFmt.setSampleFormat(QAudioFormat::Float);
549 promise->addResult(std::pair{ std::move(floatData), floatFmt });
552 decoders->removeDecoder(decoder);
556 decoder.get(), qOverload<QAudioDecoder::Error>(&QAudioDecoder::error), decoder.get(),
557 [promise, weakDecoder = std::weak_ptr<QAudioDecoder>(decoder)](QAudioDecoder::Error) {
558 auto decoder = weakDecoder.lock();
563 promise->addResult(q23::unexpected(QSampleLoadError::DecoderError));
565 decoders->removeDecoder(decoder);
568 decoder->setSourceDevice(buffer);
573bool QSampleCache::isCached(
const QUrl &url, std::optional<
int> targetSampleRate)
const
575 std::lock_guard guard(m_mutex);
576 using namespace QtMultimediaPrivate;
578 const SampleKey key{ url, asSampleRate(targetSampleRate) };
579 return m_loadedSamples.find(key) != m_loadedSamples.end()
580 || m_pendingSamples.find(key) != m_pendingSamples.end();
583QFuture<SharedSamplePtr>
584QSampleCache::requestSampleFuture(
const QUrl &url, std::optional<
int> targetSampleRate,
585 std::optional<SampleSourceType> forceSourceType)
587 std::lock_guard guard(m_mutex);
588 using namespace QtMultimediaPrivate;
590 auto targetRate = asSampleRate(targetSampleRate);
591 const SampleKey key{ url, targetRate };
593 auto promise = std::make_shared<QPromise<SharedSamplePtr>>();
594 auto future = promise->future();
597 auto found = m_loadedSamples.find(key);
598 if (found != m_loadedSamples.end()) {
599 SharedSamplePtr foundSample = found->second.lock();
600 Q_ASSERT(foundSample);
601 Q_ASSERT(foundSample->state() == QSample::Ready);
603 promise->addResult(std::move(foundSample));
609 auto pending = m_pendingSamples.find(key);
610 if (pending != m_pendingSamples.end()) {
611 pending->second.second.append(promise);
616 SharedSamplePtr sample = std::make_shared<QSample>(url,
this, targetRate);
617 m_pendingSamples.emplace(key, std::pair{ sample, QList<SharedSamplePromise>{ promise } });
619 QFuture<SampleLoadResult> futureResult = [&] {
621 if (threadPool()->maxThreadCount() > 0)
622 return QtConcurrent::run(threadPool(), [url, type = forceSourceType] {
623 return loadSample(url, type);
626 return loadSampleAsync(url, forceSourceType);
629 futureResult.then(
this,
630 [
this, key, targetRate,
631 sample = std::move(sample)](SampleLoadResult loadResult)
mutable {
633 QByteArray sampleData = loadResult->first;
634 QAudioFormat sampleFormat = loadResult->second;
636 if (targetRate && sampleFormat.sampleRate() != qToUnderlying(*targetRate)) {
637 const int rate = qToUnderlying(*targetRate);
638 const qsizetype totalFloats = sampleData.size() / qsizetype(
sizeof(
float));
639 QSpan<
const float> inputSpan{
640 reinterpret_cast<
const float *>(sampleData.constData()),
643 sampleData = QAudioHelperInternal::resampleAudioCatmullRom(
644 inputSpan, sampleFormat.channelCount(),
645 sampleFormat.sampleRate(), rate);
646 sampleFormat.setSampleRate(rate);
649 sample->setData(std::move(sampleData), sampleFormat);
654 std::lock_guard guard(m_mutex);
656 auto pending = m_pendingSamples.find(key);
657 if (pending != m_pendingSamples.end()) {
658 for (
auto &promise : pending->second.second) {
660 promise->addResult(loadResult ? sample :
nullptr);
666 m_loadedSamples.emplace(key, sample);
668 if (pending != m_pendingSamples.end())
669 m_pendingSamples.erase(pending);
676void QSampleCache::removeUnreferencedSample(
const QUrl &url,
677 std::optional<SampleRate> targetSampleRate)
679 std::lock_guard guard(m_mutex);
680 m_loadedSamples.erase(SampleKey{ url, targetSampleRate });
685#if !QT_CONFIG(thread)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)