6#include <QtMultimedia/qaudiobuffer.h>
7#include <QtMultimedia/qaudiodecoder.h>
8#include <QtMultimedia/private/qaudiohelpers_p.h>
9#include <QtMultimedia/private/qdrawavaudiodecoder_p.h>
10#include <QtMultimedia/private/qmultimediautils_p.h>
11#include <QtConcurrent/qtconcurrentrun.h>
12#include <QtCore/qapplicationstatic.h>
13#include <QtCore/qbuffer.h>
14#include <QtCore/qcoreapplication.h>
15#include <QtCore/qdebug.h>
16#include <QtCore/qeventloop.h>
17#include <QtCore/qfile.h>
18#include <QtCore/qfuturewatcher.h>
19#include <QtCore/qloggingcategory.h>
20#include <QtCore/qmutex.h>
23# include <QtNetwork/qnetworkaccessmanager.h>
24# include <QtNetwork/qnetworkreply.h>
25# include <QtNetwork/qnetworkrequest.h>
40QSample::QSample(QUrl url, QSampleCache *parent) : m_parent(parent), m_url(std::move(url)) { }
42QSample::QSample(QUrl url, QSampleCache *parent, std::optional<SampleRate> targetSampleRate)
43 : m_parent(parent), m_url(std::move(url)), m_targetSampleRate(targetSampleRate)
51 m_parent->removeUnreferencedSample(m_url, m_targetSampleRate);
53 qCDebug(qLcSampleCache) <<
"~QSample" <<
this <<
": deleted [" << m_url <<
"]" << QThread::currentThread();
56void QSample::setError()
58 m_state = State::Error;
61void QSample::setData(QByteArray data, QAudioFormat format)
63 m_state = State::Ready;
64 m_soundData = std::move(data);
65 m_audioFormat = format;
68QSample::State QSample::state()
const
73void QSample::clearParent()
80Q_APPLICATION_STATIC(QSampleCache, sampleCache)
82QSampleCache *QSampleCache::instance()
88QThreadPool *QSampleCache::threadPool()
91 return QThreadPool::globalInstance();
98QSampleCache::QSampleCache(QObject *parent) : QObject(parent)
101 if (!thread()->isMainThread())
102 moveToThread(qApp->thread());
104# if !defined(Q_OS_WASM)
106 static constexpr int loaderThreadLimit = 8;
107 m_threadPool.setObjectName(
"QSampleCachePool");
108 m_threadPool.setMaxThreadCount(loaderThreadLimit);
109 m_threadPool.setExpiryTimeout(15);
110 m_threadPool.setThreadPriority(QThread::LowPriority);
111 m_threadPool.setServiceLevel(QThread::QualityOfService::Eco);
116 Q_ASSERT(qApp &&
"QApplication is still valid");
118 QSampleCache *instance = sampleCache();
120 instance->m_threadPool.clear();
121 instance->m_threadPool.waitForDone();
128QSampleCache::~QSampleCache()
130#if QT_CONFIG(thread) && !defined(Q_OS_WASM)
131 m_threadPool.clear();
132 m_threadPool.waitForDone();
135 for (
auto &entry : m_loadedSamples) {
136 auto samplePtr = entry.second.lock();
138 samplePtr->clearParent();
141 for (
auto &entry : m_pendingSamples) {
142 auto samplePtr = entry.second.first;
144 samplePtr->clearParent();
148QSampleCache::SampleLoadResult QSampleCache::loadSample(QSpan<
const char> data)
150 using namespace QtPrivate;
153 bool success = drwav_init_memory(&wavParser, data.data(), data.size(),
nullptr);
155 return q23::unexpected(QSampleLoadError::FormatError);
157 auto cleanup = qScopeGuard([&] {
158 drwav_uninit(&wavParser);
163 QAudioFormat audioFormat;
164 audioFormat.setChannelCount(wavParser.channels);
165 audioFormat.setSampleFormat(QAudioFormat::Float);
166 audioFormat.setSampleRate(wavParser.sampleRate);
167 audioFormat.setChannelConfig(
168 QAudioFormat::defaultChannelConfigForChannelCount(wavParser.channels));
170 std::optional<QByteArray> sampleData = QtMultimediaPrivate::readFramesInChunks(
171 wavParser.totalPCMFrameCount,
sizeof(
float) * wavParser.channels,
172 [&](uint64_t frameCount,
char *out) -> uint64_t {
173 return drwav_read_pcm_frames_f32(&wavParser, frameCount,
reinterpret_cast<
float *>(out));
176 return q23::unexpected(QSampleLoadError::FormatError);
179 std::move(*sampleData),
186QByteArray convertToFloat32(
const QByteArray &data,
const QAudioFormat &fmt)
188 if (fmt.sampleFormat() == QAudioFormat::Float)
191 int totalSamples = fmt.framesForBytes(data.size()) * fmt.channelCount();
194 totalSamples *
int(
sizeof(
float)),
195 Qt::Initialization::Uninitialized,
198 using namespace QAudioHelperInternal;
199 convertSampleFormat(as_bytes(QSpan{ data }), toNativeSampleFormat(fmt.sampleFormat()),
200 as_writable_bytes(QSpan{ result }), NativeSampleFormat::float32_t);
205QSampleCache::SampleLoadResult runDecoderLoop(QAudioDecoder &decoder, QEventLoop &loop)
207 using SampleLoadResult = QSampleCache::SampleLoadResult;
209 QByteArray accumulated;
211 SampleLoadResult result = q23::unexpected(QSampleLoadError::DecoderError);
213 QObject::connect(&decoder, &QAudioDecoder::bufferReady, &loop, [&] {
214 QAudioBuffer buf = decoder.read();
219 accumulated.append(buf.constData<
char>(), buf.byteCount());
222 QObject::connect(&decoder, &QAudioDecoder::finished, &loop, [&] {
223 QByteArray floatData = convertToFloat32(accumulated, fmt);
224 QAudioFormat floatFmt = fmt;
225 floatFmt.setSampleFormat(QAudioFormat::Float);
226 result = std::pair{ std::move(floatData), floatFmt };
230 QObject::connect(&decoder, qOverload<QAudioDecoder::Error>(&QAudioDecoder::error),
231 &loop, [&](QAudioDecoder::Error) {
232 result = q23::unexpected(QSampleLoadError::DecoderError);
237 if (decoder.error() != QAudioDecoder::NoError)
238 return q23::unexpected(QSampleLoadError::DecoderError);
240 loop.exec(QEventLoop::ExcludeUserInputEvents);
247QSampleCache::SampleLoadResult
248QSampleCache::loadSampleViaDecoder(std::variant<QUrl, QByteArray> arg)
251 using namespace QtMultimediaPrivate;
253 QAudioDecoder decoder;
254 if (!decoder.isSupported())
255 return q23::unexpected(QSampleLoadError::NotSupported);
259 return std::visit(qOverloadedVisitor([&](QUrl url) -> SampleLoadResult {
260 decoder.setSource(url);
261 return runDecoderLoop(decoder, loop);
262 }, [&](QByteArray data) -> SampleLoadResult {
264 buffer.setData(data);
265 if (!buffer.open(QIODevice::ReadOnly))
266 return q23::unexpected(QSampleLoadError::IoError);
267 decoder.setSourceDevice(&buffer);
269 return runDecoderLoop(decoder, loop);
274#if QT_CONFIG(network)
278Q_CONSTINIT
thread_local std::optional<QNetworkAccessManager> g_networkAccessManager;
279QNetworkAccessManager &threadLocalNetworkAccessManager()
281 if (!g_networkAccessManager.has_value()) {
282 g_networkAccessManager.emplace();
284 if (QThread::isMainThread()) {
287 g_networkAccessManager.reset();
292 return *g_networkAccessManager;
301QSampleCache::SampleLoadResult
302QSampleCache::loadSample(
const QUrl &url, std::optional<SampleSourceType> forceSourceType)
304 using namespace Qt::Literals;
306 SampleSourceType realSourceType =
307 forceSourceType.value_or(url.scheme() == u"qrc"_s || url.scheme() == u"file"_s
308 ? SampleSourceType::File
309 : SampleSourceType::NetworkManager);
311 if (realSourceType == SampleSourceType::AudioDecoder)
312 return loadSampleViaDecoder(url);
314 if (url.scheme().isEmpty())
318 return q23::unexpected(QSampleLoadError::IoError);
320 bool errorOccurred =
false;
322 std::unique_ptr<QIODevice> decoderInput;
323 if (realSourceType == SampleSourceType::File) {
324 QString locationString =
325 url.isLocalFile() ? url.toLocalFile() : u":" + url.toString(QUrl::RemoveScheme);
327 auto *file =
new QFile(locationString);
328 bool opened = file->open(QFile::ReadOnly);
330 errorOccurred =
true;
331 decoderInput.reset(file);
333#if QT_CONFIG(network)
334 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
336 if (reply->error() != QNetworkReply::NoError)
337 errorOccurred =
true;
339 connect(reply, &QNetworkReply::errorOccurred, reply,
340 [&]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
341 errorOccurred =
true;
344 decoderInput.reset(reply);
346 return q23::unexpected(QSampleLoadError::IoError);
350 if (!decoderInput->isOpen())
351 return q23::unexpected(QSampleLoadError::IoError);
353 QByteArray data = decoderInput->readAll();
354 if (data.isEmpty() || errorOccurred)
355 return q23::unexpected(QSampleLoadError::IoError);
357 SampleLoadResult result = loadSample(data);
358 if (!result && result.error() == QSampleLoadError::FormatError && !forceSourceType) {
359 qCDebug(qLcSampleCache) <<
"drwav failed, retrying via QAudioDecoder";
360 result = loadSampleViaDecoder(data);
362 qCDebug(qLcSampleCache) <<
"device-based decode failed, retrying via URL";
363 result = loadSampleViaDecoder(url);
371QFuture<QSampleCache::SampleLoadResult>
372QSampleCache::loadSampleAsync(
const QUrl &url, std::optional<SampleSourceType> forceSourceType)
374 auto promise = std::make_shared<QPromise<QSampleCache::SampleLoadResult>>();
375 auto future = promise->future();
377 auto fulfilPromise = [&](
auto &&result)
mutable {
379 promise->addResult(result);
383 using namespace Qt::Literals;
385 SampleSourceType realSourceType = (url.scheme() == u"qrc"_s || url.scheme() == u"file"_s)
386 ? SampleSourceType::File
387 : SampleSourceType::NetworkManager;
388 if (realSourceType == SampleSourceType::File) {
389 QString locationString = url.toString(QUrl::RemoveScheme);
390 if (url.scheme() == u"qrc"_s)
391 locationString = u":" + locationString;
392 QFile file{ locationString };
393 bool opened = file.open(QFile::ReadOnly);
395 fulfilPromise(q23::unexpected(QSampleLoadError::IoError));
399 QByteArray data = file.readAll();
400 if (data.isEmpty()) {
401 fulfilPromise(q23::unexpected(QSampleLoadError::IoError));
405 if (forceSourceType == SampleSourceType::AudioDecoder)
406 return loadSampleAsyncViaDecoder(data);
408 auto drwavResult = loadSample(data);
409 if (drwavResult || drwavResult.error() != QSampleLoadError::FormatError) {
410 fulfilPromise(drwavResult);
413 return loadSampleAsyncViaDecoder(data);
416#if QT_CONFIG(network)
418 QNetworkReply *reply = threadLocalNetworkAccessManager().get(QNetworkRequest(url));
420 if (reply->error() != QNetworkReply::NoError) {
421 fulfilPromise(q23::unexpected(QSampleLoadError::IoError));
422 reply->deleteLater();
426 connect(reply, &QNetworkReply::errorOccurred, reply,
427 [reply, promise]([[maybe_unused]] QNetworkReply::NetworkError errorCode) {
429 promise->addResult(q23::unexpected(QSampleLoadError::IoError));
431 reply->deleteLater();
434 connect(reply, &QNetworkReply::finished, reply,
435 [promise, reply, fulfilPromise = std::move(fulfilPromise), forceSourceType]()
mutable {
436 QByteArray data = reply->readAll();
437 if (data.isEmpty()) {
439 promise->addResult(q23::unexpected(QSampleLoadError::IoError));
441 }
else if (forceSourceType == SampleSourceType::AudioDecoder) {
442 auto decoderFuture = loadSampleAsyncViaDecoder(data);
443 decoderFuture.then([promise](SampleLoadResult result) {
445 promise->addResult(std::move(result));
449 auto drwavResult = loadSample(data);
450 if (drwavResult || drwavResult.error() != QSampleLoadError::FormatError) {
451 fulfilPromise(drwavResult);
453 auto decoderFuture = loadSampleAsyncViaDecoder(data);
454 decoderFuture.then([promise](SampleLoadResult result) {
456 promise->addResult(std::move(result));
461 reply->deleteLater();
464 fulfilPromise(q23::unexpected(QSampleLoadError::IoError));
474struct QStaticDecoderSingleton
476 void addDecoder(std::shared_ptr<QAudioDecoder> decoder)
478 std::lock_guard guard(mutex);
479 decoders.insert(std::move(decoder));
482 void removeDecoder(std::shared_ptr<QAudioDecoder> decoder)
484 std::lock_guard guard(mutex);
485 decoders.erase(decoder);
488 std::set<std::shared_ptr<QAudioDecoder>> decoders;
492Q_APPLICATION_STATIC(QStaticDecoderSingleton, decoders);
496QFuture<QSampleCache::SampleLoadResult>
497QSampleCache::loadSampleAsyncViaDecoder(QByteArray data)
506 auto promise = std::make_shared<QPromise<SampleLoadResult>>();
507 auto future = promise->future();
509 auto decoder = QtMultimediaPrivate::makeSharedDeleteLater<QAudioDecoder>();
510 if (!decoder->isSupported()) {
512 promise->addResult(q23::unexpected(QSampleLoadError::NotSupported));
517 auto *buffer =
new QBuffer(decoder.get());
518 buffer->setData(data);
519 buffer->open(QIODevice::ReadOnly);
521 auto accum = std::make_shared<QByteArray>();
522 auto fmt = std::make_shared<QAudioFormat>();
524 decoders->addDecoder(decoder);
526 QObject::connect(decoder.get(), &QAudioDecoder::bufferReady, decoder.get(),
527 [weakDecoder = std::weak_ptr<QAudioDecoder>(decoder), accum, fmt] {
528 auto decoder = weakDecoder.lock();
532 QAudioBuffer buf = decoder->read();
537 accum->append(buf.constData<
char>(), buf.byteCount());
540 QObject::connect(decoder.get(), &QAudioDecoder::finished, decoder.get(),
541 [promise, accum, fmt, weakDecoder = std::weak_ptr<QAudioDecoder>(decoder)] {
542 auto decoder = weakDecoder.lock();
546 QByteArray floatData = convertToFloat32(*accum, *fmt);
547 QAudioFormat floatFmt = *fmt;
548 floatFmt.setSampleFormat(QAudioFormat::Float);
550 promise->addResult(std::pair{ std::move(floatData), floatFmt });
553 decoders->removeDecoder(decoder);
557 decoder.get(), qOverload<QAudioDecoder::Error>(&QAudioDecoder::error), decoder.get(),
558 [promise, weakDecoder = std::weak_ptr<QAudioDecoder>(decoder)](QAudioDecoder::Error) {
559 auto decoder = weakDecoder.lock();
564 promise->addResult(q23::unexpected(QSampleLoadError::DecoderError));
566 decoders->removeDecoder(decoder);
569 decoder->setSourceDevice(buffer);
574bool QSampleCache::isCached(
const QUrl &url, std::optional<
int> targetSampleRate)
const
576 std::lock_guard guard(m_mutex);
577 using namespace QtMultimediaPrivate;
579 const SampleKey key{ url, asSampleRate(targetSampleRate) };
580 return m_loadedSamples.find(key) != m_loadedSamples.end()
581 || m_pendingSamples.find(key) != m_pendingSamples.end();
584QFuture<SharedSamplePtr>
585QSampleCache::requestSampleFuture(
const QUrl &url, std::optional<
int> targetSampleRate,
586 std::optional<SampleSourceType> forceSourceType)
588 std::lock_guard guard(m_mutex);
589 using namespace QtMultimediaPrivate;
591 auto targetRate = asSampleRate(targetSampleRate);
592 const SampleKey key{ url, targetRate };
594 auto promise = std::make_shared<QPromise<SharedSamplePtr>>();
595 auto future = promise->future();
598 auto found = m_loadedSamples.find(key);
599 if (found != m_loadedSamples.end()) {
600 SharedSamplePtr foundSample = found->second.lock();
601 Q_ASSERT(foundSample);
602 Q_ASSERT(foundSample->state() == QSample::Ready);
604 promise->addResult(std::move(foundSample));
610 auto pending = m_pendingSamples.find(key);
611 if (pending != m_pendingSamples.end()) {
612 pending->second.second.append(promise);
617 SharedSamplePtr sample = std::make_shared<QSample>(url,
this, targetRate);
618 m_pendingSamples.emplace(key, std::pair{ sample, QList<SharedSamplePromise>{ promise } });
620 QFuture<SampleLoadResult> futureResult = [&] {
622 if (threadPool()->maxThreadCount() > 0)
623 return QtConcurrent::run(threadPool(), [url, type = forceSourceType] {
624 return loadSample(url, type);
627 return loadSampleAsync(url, forceSourceType);
630 futureResult.then(
this,
631 [
this, key, targetRate,
632 sample = std::move(sample)](SampleLoadResult loadResult)
mutable {
634 QByteArray sampleData = loadResult->first;
635 QAudioFormat sampleFormat = loadResult->second;
637 if (targetRate && sampleFormat.sampleRate() != qToUnderlying(*targetRate)) {
638 const int rate = qToUnderlying(*targetRate);
639 const qsizetype totalFloats = sampleData.size() / qsizetype(
sizeof(
float));
640 QSpan<
const float> inputSpan{
641 reinterpret_cast<
const float *>(sampleData.constData()),
644 sampleData = QAudioHelperInternal::resampleAudioCatmullRom(
645 inputSpan, sampleFormat.channelCount(),
646 sampleFormat.sampleRate(), rate);
647 sampleFormat.setSampleRate(rate);
650 sample->setData(std::move(sampleData), sampleFormat);
655 std::lock_guard guard(m_mutex);
657 auto pending = m_pendingSamples.find(key);
658 if (pending != m_pendingSamples.end()) {
659 for (
auto &promise : pending->second.second) {
661 promise->addResult(loadResult ? sample :
nullptr);
667 m_loadedSamples.emplace(key, sample);
669 if (pending != m_pendingSamples.end())
670 m_pendingSamples.erase(pending);
677void QSampleCache::removeUnreferencedSample(
const QUrl &url,
678 std::optional<SampleRate> targetSampleRate)
680 std::lock_guard guard(m_mutex);
681 m_loadedSamples.erase(SampleKey{ url, targetSampleRate });
686#if !QT_CONFIG(thread)
QT_BEGIN_NAMESPACE Q_STATIC_LOGGING_CATEGORY(lcSynthesizedIterableAccess, "qt.iterable.synthesized", QtWarningMsg)