Source code

Revision control

Copy as Markdown

// 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