Revision control
++ // Copyright (C) 2021 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #ifndef QSAMPLECACHE_P_H
++ #define QSAMPLECACHE_P_H
++
++ //
++ // W A R N I N G
++ // -------------
++ //
++ // This file is not part of the Qt API. It exists purely as an
++ // implementation detail. This header file may change from version to
++ // version without notice, or even be removed.
++ //
++ // We mean it.
++ //
++
++ #include <QtMultimedia/qaudioformat.h>
++ #include <QtCore/private/qexpected_p.h>
++ #include <QtCore/private/qglobal_p.h>
++ #include <QtCore/qfuture.h>
++ #include <QtCore/qmutex.h>
++ #include <QtCore/qobject.h>
++ #include <QtCore/qpointer.h>
++ #include <QtCore/qset.h>
++ #include <QtCore/qspan.h>
++ #include <QtCore/qthread.h>
++ #include <QtCore/qurl.h>
++
++ #include <memory>
++ #include <optional>
++
++ QT_BEGIN_NAMESPACE
++
++ enum class QSampleLoadError : uint8_t
++ {
++ IoError,
++ FormatError,
++ DecoderError,
++ NotSupported,
++ };
++
++ namespace QtMultimediaPrivate {
++
++ enum class SampleRate : int;
++ inline std::optional<SampleRate> asSampleRate(std::optional<int> arg)
++ {
++ if (!arg)
++ return std::nullopt;
++ return SampleRate(*arg);
++ }
++
++ } // namespace QtMultimediaPrivate
++
++ class QSampleCache;
++
++ class Q_MULTIMEDIA_EXPORT QSample
++ {
++ public:
++ friend class QSampleCache;
++ using SampleRate = QtMultimediaPrivate::SampleRate;
++
++ enum State : uint8_t {
++ Creating,
++ Error,
++ Ready,
++ };
++ using SharedSamplePromise = QSharedPointer<QPromise<q23::expected<QSample *, QSample::State>>>;
++ ~QSample();
++
++ State state() const;
++ const QByteArray& data() const { Q_ASSERT(state() == Ready); return m_soundData; }
++ QSpan<const float> dataAsFloatSpan() const
++ {
++ Q_ASSERT(state() == Ready);
++ Q_ASSERT(m_audioFormat.sampleFormat() == QAudioFormat::Float);
++ return QSpan<const float>(reinterpret_cast<const float *>(m_soundData.constData()),
++ m_audioFormat.framesForBytes(m_soundData.size())
++ * m_audioFormat.channelCount());
++ }
++
++ const QAudioFormat &format() const
++ {
++ Q_ASSERT(state() == Ready);
++ return m_audioFormat;
++ }
++ qsizetype frameCount() const
++ {
++ Q_ASSERT(state() == Ready);
++ return m_audioFormat.framesForBytes(m_soundData.size());
++ }
++
++ void setError();
++ void setData(QByteArray, QAudioFormat);
++
++ QSample(QUrl url, QSampleCache *parent);
++ QSample(QUrl url, QSampleCache *parent, std::optional<SampleRate> targetSampleRate);
++
++ // For testing only
++ QSample(QByteArray data, QAudioFormat format)
++ : m_parent(nullptr), m_soundData(std::move(data)), m_audioFormat(format), m_url(), m_state(Ready) {}
++
++ private:
++ QSample();
++
++ QSampleCache *m_parent;
++ QByteArray m_soundData;
++ QAudioFormat m_audioFormat;
++ const QUrl m_url;
++ const std::optional<SampleRate> m_targetSampleRate;
++ State m_state = State::Creating;
++
++ friend class QSampleCache;
++ void clearParent();
++ };
++
++ using SharedSamplePtr = std::shared_ptr<QSample>;
++ using WeakSamplePtr = std::weak_ptr<QSample>;
++
++ class Q_MULTIMEDIA_EXPORT QSampleCache : public QObject
++ {
++ public:
++ friend class QSample;
++
++ enum class SampleSourceType : uint8_t {
++ File,
++ NetworkManager,
++ AudioDecoder,
++ };
++
++ static QSampleCache *instance();
++
++ explicit QSampleCache(QObject *parent = nullptr);
++ ~QSampleCache() override;
++
++ QFuture<SharedSamplePtr>
++ requestSampleFuture(const QUrl &, std::optional<int> targetSampleRate = std::nullopt,
++ std::optional<SampleSourceType> forceSourceType = std::nullopt);
++
++ // functions below are for testing only
++ bool isCached(const QUrl &url, std::optional<int> targetSampleRate = std::nullopt) const;
++
++ using SampleLoadResult = q23::expected<std::pair<QByteArray, QAudioFormat>, QSampleLoadError>;
++
++ static SampleLoadResult loadSample(QSpan<const char>);
++ static SampleLoadResult loadSampleViaDecoder(std::variant<QUrl, QByteArray>);
++ static QFuture<SampleLoadResult> loadSampleAsyncViaDecoder(QByteArray);
++
++ private:
++ std::unique_ptr<QIODevice> createStreamForSample(QSample &sample);
++ #if QT_CONFIG(thread)
++ QThreadPool *threadPool();
++ #endif
++
++ private:
++ using SharedSamplePromise = std::shared_ptr<QPromise<SharedSamplePtr>>;
++
++ mutable QRecursiveMutex m_mutex;
++ using SampleRate = QtMultimediaPrivate::SampleRate;
++
++ using SampleKey = std::pair<QUrl, std::optional<SampleRate>>;
++ std::map<SampleKey, WeakSamplePtr> m_loadedSamples;
++ std::map<SampleKey, std::pair<SharedSamplePtr, QList<SharedSamplePromise>>> m_pendingSamples;
++
++ void removeUnreferencedSample(const QUrl &, std::optional<SampleRate> targetSampleRate);
++
++ #if QT_CONFIG(thread)
++ static SampleLoadResult loadSample(const QUrl &,
++ std::optional<SampleSourceType> = std::nullopt);
++ # ifndef Q_OS_WASM
++ QThreadPool m_threadPool{ this };
++ # endif
++ #endif
++ QFuture<SampleLoadResult> loadSampleAsync(const QUrl &,
++ std::optional<SampleSourceType> = std::nullopt);
++ };
++
++ QT_END_NAMESPACE
++
++ #endif // QSAMPLECACHE_P_H