Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qsamplecache_p.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qsamplecache_p.h"
5#include "qwavedecoder.h"
6
7#include <QtNetwork/QNetworkAccessManager>
8#include <QtNetwork/QNetworkReply>
9#include <QtNetwork/QNetworkRequest>
10
11#include <QtCore/QDebug>
12#include <QtCore/qloggingcategory.h>
13
14static Q_LOGGING_CATEGORY(qLcSampleCache, "qt.multimedia.samplecache")
15
16#include <mutex>
17
19
20
67 : QObject(parent)
68 , m_networkAccessManager(nullptr)
69 , m_capacity(0)
70 , m_usage(0)
71 , m_loadingRefCount(0)
72{
73 m_loadingThread.setObjectName(QLatin1String("QSampleCache::LoadingThread"));
74}
75
76QNetworkAccessManager& QSampleCache::networkAccessManager()
77{
78 if (!m_networkAccessManager)
79 m_networkAccessManager = new QNetworkAccessManager();
80 return *m_networkAccessManager;
81}
82
84{
85 const std::lock_guard<QRecursiveMutex> locker(m_mutex);
86
87 m_loadingThread.quit();
88 m_loadingThread.wait();
89
90 // Killing the loading thread means that no samples can be
91 // deleted using deleteLater. And some samples that had deleteLater
92 // already called won't have been processed (m_staleSamples)
93 for (auto it = m_samples.cbegin(), end = m_samples.cend(); it != end; ++it)
94 delete it.value();
95
96 const auto copyStaleSamples = m_staleSamples; //deleting a sample does affect the m_staleSamples list, but we create a copy
97 for (QSample* sample : copyStaleSamples)
98 delete sample;
99
100 delete m_networkAccessManager;
101}
102
103void QSampleCache::loadingRelease()
104{
105 QMutexLocker locker(&m_loadingMutex);
106 m_loadingRefCount--;
107 if (m_loadingRefCount == 0) {
108 if (m_loadingThread.isRunning()) {
109 if (m_networkAccessManager) {
110 m_networkAccessManager->deleteLater();
111 m_networkAccessManager = nullptr;
112 }
113 m_loadingThread.exit();
114 }
115 }
116}
117
119{
120 return m_loadingThread.isRunning();
121}
122
124{
125 const std::lock_guard<QRecursiveMutex> locker(m_mutex);
126 return m_samples.contains(url);
127}
128
130{
131 //lock and add first to make sure live loadingThread will not be killed during this function call
132 m_loadingMutex.lock();
133 const bool needsThreadStart = m_loadingRefCount == 0;
134 m_loadingRefCount++;
135 m_loadingMutex.unlock();
136
137 qCDebug(qLcSampleCache) << "QSampleCache: request sample [" << url << "]";
138 std::unique_lock<QRecursiveMutex> locker(m_mutex);
140 QSample* sample;
141 if (it == m_samples.end()) {
142 if (needsThreadStart) {
143 // Previous thread might be finishing, need to wait for it. If not, this is a no-op.
144 m_loadingThread.wait();
145 m_loadingThread.start();
146 }
147 sample = new QSample(url, this);
148 m_samples.insert(url, sample);
149#if QT_CONFIG(thread)
150 sample->moveToThread(&m_loadingThread);
151#endif
152 } else {
153 sample = *it;
154 }
155
156 sample->addRef();
157 locker.unlock();
158
159 sample->loadIfNecessary();
160 return sample;
161}
162
164{
165 const std::lock_guard<QRecursiveMutex> locker(m_mutex);
166 if (m_capacity == capacity)
167 return;
168 qCDebug(qLcSampleCache) << "QSampleCache: capacity changes from " << m_capacity << "to " << capacity;
169 if (m_capacity > 0 && capacity <= 0) { //memory management strategy changed
170 for (QMap<QUrl, QSample*>::iterator it = m_samples.begin(); it != m_samples.end();) {
171 QSample* sample = *it;
172 if (sample->m_ref == 0) {
173 unloadSample(sample);
174 it = m_samples.erase(it);
175 } else {
176 ++it;
177 }
178 }
179 }
180
181 m_capacity = capacity;
182 refresh(0);
183}
184
185// Called locked
186void QSampleCache::unloadSample(QSample *sample)
187{
188 m_usage -= sample->m_soundData.size();
189 m_staleSamples.insert(sample);
190 sample->deleteLater();
191}
192
193// Called in both threads
194void QSampleCache::refresh(qint64 usageChange)
195{
196 const std::lock_guard<QRecursiveMutex> locker(m_mutex);
197 m_usage += usageChange;
198 if (m_capacity <= 0 || m_usage <= m_capacity)
199 return;
200
201 qint64 recoveredSize = 0;
202
203 //free unused samples to keep usage under capacity limit.
204 for (QMap<QUrl, QSample*>::iterator it = m_samples.begin(); it != m_samples.end();) {
205 QSample* sample = *it;
206 if (sample->m_ref > 0) {
207 ++it;
208 continue;
209 }
210 recoveredSize += sample->m_soundData.size();
211 unloadSample(sample);
212 it = m_samples.erase(it);
213 if (m_usage <= m_capacity)
214 return;
215 }
216
217 qCDebug(qLcSampleCache) << "QSampleCache: refresh(" << usageChange
218 << ") recovered size =" << recoveredSize
219 << "new usage =" << m_usage;
220
221 if (m_usage > m_capacity)
222 qWarning() << "QSampleCache: usage[" << m_usage << " out of limit[" << m_capacity << "]";
223}
224
225// Called in both threads
226void QSampleCache::removeUnreferencedSample(QSample *sample)
227{
228 const std::lock_guard<QRecursiveMutex> locker(m_mutex);
229 m_staleSamples.remove(sample);
230}
231
232// Called in loader thread (since this lives in that thread)
233// Also called from application thread after loader thread dies.
234QSample::~QSample()
235{
236 // Remove ourselves from our parent
237 m_parent->removeUnreferencedSample(this);
238
239 QMutexLocker locker(&m_mutex);
240 qCDebug(qLcSampleCache) << "~QSample" << this << ": deleted [" << m_url << "]" << QThread::currentThread();
241 cleanup();
242}
243
244// Called in application thread
245void QSample::loadIfNecessary()
246{
247 QMutexLocker locker(&m_mutex);
248 if (m_state == QSample::Error || m_state == QSample::Creating) {
249 m_state = QSample::Loading;
251 } else {
252 qobject_cast<QSampleCache*>(m_parent)->loadingRelease();
253 }
254}
255
256// Called in application thread
257bool QSampleCache::notifyUnreferencedSample(QSample* sample)
258{
259 if (m_loadingThread.isRunning())
260 m_loadingThread.wait();
261
262 const std::lock_guard<QRecursiveMutex> locker(m_mutex);
263
264 if (m_capacity > 0)
265 return false;
266 m_samples.remove(sample->m_url);
267 unloadSample(sample);
268 return true;
269}
270
271// Called in application thread
273{
274 QMutexLocker locker(&m_mutex);
275 qCDebug(qLcSampleCache) << "Sample:: release" << this << QThread::currentThread() << m_ref;
276 if (--m_ref == 0) {
277 locker.unlock();
278 m_parent->notifyUnreferencedSample(this);
279 }
280}
281
282// Called in dtor and when stream is loaded
283// must be called locked.
284void QSample::cleanup()
285{
286 qCDebug(qLcSampleCache) << "QSample: cleanup";
287 if (m_waveDecoder) {
288 m_waveDecoder->disconnect(this);
289 m_waveDecoder->deleteLater();
290 }
291 if (m_stream) {
292 m_stream->disconnect(this);
293 m_stream->deleteLater();
294 }
295
296 m_waveDecoder = nullptr;
297 m_stream = nullptr;
298}
299
300// Called in application thread
301void QSample::addRef()
302{
303 m_ref++;
304}
305
306// Called in loading thread
307void QSample::readSample()
308{
309#if QT_CONFIG(thread)
310 Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread"));
311#endif
312 QMutexLocker m(&m_mutex);
313 qint64 read = m_waveDecoder->read(m_soundData.data() + m_sampleReadLength,
314 qMin(m_waveDecoder->bytesAvailable(),
315 qint64(m_waveDecoder->size() - m_sampleReadLength)));
316 qCDebug(qLcSampleCache) << "QSample: readSample" << read;
317 if (read > 0)
318 m_sampleReadLength += read;
319 if (m_sampleReadLength < m_waveDecoder->size())
320 return;
321 Q_ASSERT(m_sampleReadLength == qint64(m_soundData.size()));
322 onReady();
323}
324
325// Called in loading thread
326void QSample::decoderReady()
327{
328#if QT_CONFIG(thread)
329 Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread"));
330#endif
331 QMutexLocker m(&m_mutex);
332 qCDebug(qLcSampleCache) << "QSample: decoder ready";
333 m_parent->refresh(m_waveDecoder->size());
334
335 m_soundData.resize(m_waveDecoder->size());
336 m_sampleReadLength = 0;
337 qint64 read = m_waveDecoder->read(m_soundData.data(), m_waveDecoder->size());
338 qCDebug(qLcSampleCache) << " bytes read" << read;
339 if (read > 0)
340 m_sampleReadLength += read;
341 if (m_sampleReadLength >= m_waveDecoder->size())
342 onReady();
343}
344
345// Called in all threads
347{
348 QMutexLocker m(&m_mutex);
349 return m_state;
350}
351
352// Called in loading thread
353// Essentially a second ctor, doesn't need locks (?)
354void QSample::load()
355{
356#if QT_CONFIG(thread)
357 Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread"));
358#endif
359 qCDebug(qLcSampleCache) << "QSample: load [" << m_url << "]";
360 QNetworkReply *reply = m_parent->networkAccessManager().get(QNetworkRequest(m_url));
361 m_stream = reply;
362 connect(reply, &QNetworkReply::errorOccurred, this, &QSample::loadingError);
363 m_waveDecoder = new QWaveDecoder(m_stream);
364 connect(m_waveDecoder, &QWaveDecoder::formatKnown, this, &QSample::decoderReady);
365 connect(m_waveDecoder, &QWaveDecoder::parsingError, this, &QSample::decoderError);
366 connect(m_waveDecoder, &QIODevice::readyRead, this, &QSample::readSample);
367
368 m_waveDecoder->open(QIODevice::ReadOnly);
369}
370
371void QSample::loadingError(QNetworkReply::NetworkError errorCode)
372{
373#if QT_CONFIG(thread)
374 Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread"));
375#endif
376 QMutexLocker m(&m_mutex);
377 qCDebug(qLcSampleCache) << "QSample: loading error" << errorCode;
378 cleanup();
379 m_state = QSample::Error;
380 qobject_cast<QSampleCache*>(m_parent)->loadingRelease();
381 emit error();
382}
383
384// Called in loading thread
385void QSample::decoderError()
386{
387#if QT_CONFIG(thread)
388 Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread"));
389#endif
390 QMutexLocker m(&m_mutex);
391 qCDebug(qLcSampleCache) << "QSample: decoder error";
392 cleanup();
393 m_state = QSample::Error;
394 qobject_cast<QSampleCache*>(m_parent)->loadingRelease();
395 emit error();
396}
397
398// Called in loading thread from decoder when sample is done. Locked already.
399void QSample::onReady()
400{
401#if QT_CONFIG(thread)
402 Q_ASSERT(QThread::currentThread()->objectName() == QLatin1String("QSampleCache::LoadingThread"));
403#endif
404 m_audioFormat = m_waveDecoder->audioFormat();
405 qCDebug(qLcSampleCache) << "QSample: load ready format:" << m_audioFormat;
406 cleanup();
407 m_state = QSample::Ready;
408 qobject_cast<QSampleCache*>(m_parent)->loadingRelease();
409 emit ready();
410}
411
412// Called in application thread, then moved to loader thread
414 : m_parent(parent)
415 , m_stream(nullptr)
416 , m_waveDecoder(nullptr)
417 , m_url(url)
418 , m_sampleReadLength(0)
419 , m_state(Creating)
420 , m_ref(0)
421{
422}
423
425
426#include "moc_qsamplecache_p.cpp"
char * data()
\macro QT_NO_CAST_FROM_BYTEARRAY
Definition qbytearray.h:611
qsizetype size() const noexcept
Returns the number of bytes in this byte array.
Definition qbytearray.h:494
void resize(qsizetype size)
Sets the size of the byte array to size bytes.
void readyRead()
This signal is emitted once every time new data is available for reading from the device's current re...
qint64 read(char *data, qint64 maxlen)
Reads at most maxSize bytes from the device into data, and returns the number of bytes read.
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
iterator erase(const_iterator it)
Definition qmap.h:619
bool contains(const Key &key) const
Definition qmap.h:341
const_iterator cend() const
Definition qmap.h:605
size_type remove(const Key &key)
Definition qmap.h:300
const_iterator cbegin() const
Definition qmap.h:601
iterator find(const Key &key)
Definition qmap.h:641
iterator begin()
Definition qmap.h:598
iterator end()
Definition qmap.h:602
\inmodule QtCore
Definition qmutex.h:313
void unlock() noexcept
Unlocks this mutex locker.
Definition qmutex.h:319
void unlock() noexcept
Unlocks the mutex.
Definition qmutex.h:289
void lock() noexcept
Locks the mutex.
Definition qmutex.h:286
The QNetworkAccessManager class allows the application to send network requests and receive replies.
QNetworkReply * get(const QNetworkRequest &request)
Posts a request to obtain the contents of the target request and returns a new QNetworkReply object o...
The QNetworkReply class contains the data and headers for a request sent with QNetworkAccessManager.
void errorOccurred(QNetworkReply::NetworkError)
NetworkError
Indicates all possible error conditions found during the processing of the request.
The QNetworkRequest class holds a request to be sent with QNetworkAccessManager.
\inmodule QtCore
Definition qobject.h:103
static QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
\threadsafe
Definition qobject.cpp:2960
QString objectName
the name of this object
Definition qobject.h:107
static bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *member)
\threadsafe
Definition qobject.cpp:3236
bool moveToThread(QThread *thread QT6_DECL_NEW_OVERLOAD_TAIL)
Changes the thread affinity for this object and its children and returns true on success.
Definition qobject.cpp:1643
Q_WEAK_OVERLOAD void setObjectName(const QString &name)
Sets the object's name to name.
Definition qobject.h:127
void deleteLater()
\threadsafe
Definition qobject.cpp:2435
bool isLoading() const
friend class QSample
QSample * requestSample(const QUrl &url)
QSampleCache(QObject *parent=nullptr)
void setCapacity(qint64 capacity)
bool isCached(const QUrl &url) const
void ready()
void error()
State state() const
QSample(const QUrl &url, QSampleCache *parent)
bool remove(const T &value)
Definition qset.h:63
iterator insert(const T &value)
Definition qset.h:155
void start(Priority=InheritPriority)
Definition qthread.cpp:996
bool isRunning() const
Definition qthread.cpp:1064
static QThread * currentThread()
Definition qthread.cpp:1039
bool wait(QDeadlineTimer deadline=QDeadlineTimer(QDeadlineTimer::Forever))
Definition qthread.cpp:1023
void quit()
Definition qthread.cpp:1008
void exit(int retcode=0)
Definition qthread.cpp:1013
\inmodule QtCore
Definition qurl.h:94
qint64 bytesAvailable() const override
Returns the number of bytes that are available for reading.
qint64 size() const override
For open random-access devices, this function returns the size of the device.
void formatKnown()
QAudioFormat audioFormat() const
bool open(QIODevice::OpenMode mode) override
void parsingError()
QSet< QString >::iterator it
Combined button and popup list for selecting options.
@ QueuedConnection
NSUInteger capacity
#define qWarning
Definition qlogging.h:166
#define Q_LOGGING_CATEGORY(name,...)
#define qCDebug(category,...)
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
const GLfloat * m
GLenum GLuint GLintptr GLsizeiptr size
[1]
GLuint GLuint end
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
QLatin1StringView QLatin1String
Definition qstringfwd.h:31
#define emit
long long qint64
Definition qtypes.h:60
ReturnedValue read(const char *data)
QUrl url("example.com")
[constructor-url-reference]
QObject::connect nullptr
QNetworkReply * reply
static bool invokeMethod(QObject *obj, const char *member, Qt::ConnectionType, QGenericReturnArgument ret, QGenericArgument val0=QGenericArgument(nullptr), QGenericArgument val1=QGenericArgument(), QGenericArgument val2=QGenericArgument(), QGenericArgument val3=QGenericArgument(), QGenericArgument val4=QGenericArgument(), QGenericArgument val5=QGenericArgument(), QGenericArgument val6=QGenericArgument(), QGenericArgument val7=QGenericArgument(), QGenericArgument val8=QGenericArgument(), QGenericArgument val9=QGenericArgument())
\threadsafe This is an overloaded member function, provided for convenience. It differs from the abov...