Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qsoundeffectsynchronous.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
5
6#include <QtMultimedia/qmediadevices.h>
7#include <QtMultimedia/private/qaudiosystem_p.h>
8#include <QtMultimedia/private/qplatformaudiodevices_p.h>
9#include <QtMultimedia/private/qplatformaudioresampler_p.h>
10#include <QtMultimedia/private/qplatformmediaintegration_p.h>
11
12#ifdef Q_OS_WIN
13# include <QtMultimedia/private/qwindows_wasapi_warmup_client_p.h>
14#endif
15
17
18void QSoundEffectPrivateSynchronous::AudioSinkDeleter::operator()(QAudioSink *sink) const
19{
20 sink->stop();
21 // Investigate:should we just delete?
22 sink->deleteLater();
23}
24
25QSoundEffectPrivateSynchronous::QSoundEffectPrivateSynchronous(QSoundEffect *q,
26 const QAudioDevice &audioDevice)
27 : q_ptr(q), m_audioDevice(audioDevice)
28{
29 open(QIODevice::ReadOnly);
30}
31
32QSoundEffectPrivateSynchronous::~QSoundEffectPrivateSynchronous()
33{
34 m_audioSink.reset();
35 m_sample.reset();
36}
37
38qint64 QSoundEffectPrivateSynchronous::readData(char *data, qint64 len)
39{
40 qCDebug(qLcSoundEffect) << this << "readData" << len << m_runningCount;
41 if (!len)
42 return 0;
43 if (m_sample->state() != QSample::Ready)
44 return 0;
45 if (m_runningCount == 0 || !m_playing)
46 return 0;
47
48 qint64 bytesWritten = 0;
49
50 const int sampleSize = m_audioBuffer.byteCount();
51 const char *sampleData = m_audioBuffer.constData<char>();
52
53 while (len && m_runningCount) {
54 int toWrite = qMax(0, qMin(sampleSize - m_offset, len));
55 memcpy(data, sampleData + m_offset, toWrite);
56 bytesWritten += toWrite;
57 data += toWrite;
58 len -= toWrite;
59 m_offset += toWrite;
60 if (m_offset >= sampleSize) {
61 if (m_runningCount > 0)
62 setLoopsRemaining(m_runningCount - 1);
63 m_offset = 0;
64 }
65 }
66
67 return bytesWritten;
68}
69
70qint64 QSoundEffectPrivateSynchronous::writeData(const char *data, qint64 len)
71{
72 Q_UNUSED(data);
73 Q_UNUSED(len);
74 return 0;
75}
76
77qint64 QSoundEffectPrivateSynchronous::size() const
78{
79 if (m_sample->state() != QSample::Ready)
80 return 0;
81 return m_loopCount == QSoundEffect::Infinite ? 0 : m_loopCount * m_audioBuffer.byteCount();
82}
83
84qint64 QSoundEffectPrivateSynchronous::bytesAvailable() const
85{
86 if (m_sample->state() != QSample::Ready)
87 return 0;
88 if (m_loopCount == QSoundEffect::Infinite)
89 return std::numeric_limits<qint64>::max();
90 return m_runningCount * m_audioBuffer.byteCount() - m_offset;
91}
92
93bool QSoundEffectPrivateSynchronous::isSequential() const
94{
95 return m_loopCount == QSoundEffect::Infinite;
96}
97
98bool QSoundEffectPrivateSynchronous::atEnd() const
99{
100 return m_runningCount == 0;
101}
102
103void QSoundEffectPrivateSynchronous::setLoopsRemaining(int loopsRemaining)
104{
105 if (m_runningCount == loopsRemaining)
106 return;
107 qCDebug(qLcSoundEffect) << this << "setLoopsRemaining " << loopsRemaining;
108 m_runningCount = loopsRemaining;
109 emit q_ptr->loopsRemainingChanged();
110}
111
112void QSoundEffectPrivateSynchronous::setStatus(QSoundEffect::Status status)
113{
114 qCDebug(qLcSoundEffect) << this << "setStatus" << status;
115 if (m_status == status)
116 return;
117 bool oldLoaded = q_ptr->isLoaded();
118 m_status = status;
119 emit q_ptr->statusChanged();
120 if (oldLoaded != q_ptr->isLoaded())
121 emit q_ptr->loadedChanged();
122}
123
124void QSoundEffectPrivateSynchronous::setPlaying(bool playing)
125{
126 qCDebug(qLcSoundEffect) << this << "setPlaying(" << playing << ")" << m_playing;
127 if (m_audioSink) {
128 m_audioSink->reset();
129 if (playing && !m_sampleReady)
130 return;
131 }
132
133 if (m_playing == playing)
134 return;
135 m_playing = playing;
136
137 if (m_audioSink && playing) {
138 m_audioSink->start(this);
139#ifdef Q_OS_WIN
140 QtMultimediaPrivate::refreshWarmupClient();
141#endif
142 }
143
144 emit q_ptr->playingChanged();
145}
146
147bool QSoundEffectPrivateSynchronous::updateAudioOutput()
148{
149 const auto audioDevice =
150 m_audioDevice.isNull() ? QMediaDevices::defaultAudioOutput() : m_audioDevice;
151
152 if (audioDevice.isNull()) {
153 // We are likely on a virtual machine, for example in CI
154 qCCritical(qLcSoundEffect) << "Failed to update audio output. No audio devices available.";
155 setStatus(QSoundEffect::Error);
156 return false;
157 }
158
159 if (m_audioDevice.isNull()) {
160 q_ptr->setAudioDevice(audioDevice); // Updates m_audioDevice and emits audioDeviceChanged
161 }
162
163 m_audioBuffer = {};
164
165 Q_ASSERT(m_sample);
166
167 const auto &sampleFormat = m_sample->format();
168 const auto sampleChannelConfig =
169 sampleFormat.channelConfig() == QAudioFormat::ChannelConfigUnknown
170 ? QAudioFormat::defaultChannelConfigForChannelCount(sampleFormat.channelCount())
171 : sampleFormat.channelConfig();
172
173 if (sampleChannelConfig != audioDevice.channelConfiguration()
174 && audioDevice.channelConfiguration() != QAudioFormat::ChannelConfigUnknown) {
175 qCDebug(qLcSoundEffect) << "Create resampler for channels mapping: config"
176 << sampleFormat.channelConfig() << "=> config"
177 << audioDevice.channelConfiguration();
178 auto outputFormat = sampleFormat;
179 outputFormat.setChannelConfig(audioDevice.channelConfiguration());
180
181 const auto resampler = QPlatformMediaIntegration::instance()->createAudioResampler(
182 m_sample->format(), outputFormat);
183 if (resampler)
184 m_audioBuffer = resampler.value()->resample(m_sample->data().constData(),
185 m_sample->data().size());
186 else
187 qCDebug(qLcSoundEffect) << "Cannot create resampler for channels mapping";
188 }
189
190 if (!m_audioBuffer.isValid())
191 m_audioBuffer = QAudioBuffer(m_sample->data(), m_sample->format());
192
193 m_audioSink.reset(new QAudioSink(audioDevice, m_audioBuffer.format()));
194
195 QObject::connect(m_audioSink.get(), &QAudioSink::stateChanged, this,
196 [this](QAudio::State state) {
197 this->stateChanged(state);
198 });
199
200 if (!m_muted)
201 m_audioSink->setVolume(m_volume);
202 else
203 m_audioSink->setVolume(0);
204
205 QPlatformAudioSink *sinkPrivate = QPlatformAudioSink::get(*m_audioSink);
206 sinkPrivate->setRole(QtMultimediaPrivate::AudioEndpointRole::SoundEffect);
207
208 return true;
209}
210
211void QSoundEffectPrivateSynchronous::decoderError()
212{
213 qWarning("QSoundEffect(qaudio): Error decoding source %ls", qUtf16Printable(m_url.toString()));
214
215 m_playing = false;
216 setStatus(QSoundEffect::Error);
217}
218
219void QSoundEffectPrivateSynchronous::sampleReady(SharedSamplePtr sample)
220{
221 if (m_status == QSoundEffect::Error)
222 return;
223
224 m_sample = std::move(sample);
225
226 qCDebug(qLcSoundEffect) << this << "sampleReady: sample size:" << m_sample->data().size();
227 if (!m_audioSink) {
228 if (!updateAudioOutput()) // Create audio sink
229 return; // Returns if no audio devices are available
230 }
231
232 m_sampleReady = true;
233 setStatus(QSoundEffect::Ready);
234
235 if (m_playing && m_audioSink->state() == QAudio::StoppedState) {
236 qCDebug(qLcSoundEffect) << this << "starting playback on audiooutput";
237 m_audioSink->start(this);
238 }
239}
240
241int QSoundEffectPrivateSynchronous::loopCount() const
242{
243 return m_loopCount;
244}
245
246bool QSoundEffectPrivateSynchronous::setLoopCount(int loopCount)
247{
248 if (loopCount == 0)
249 loopCount = 1;
250
251 if (loopCount == m_loopCount)
252 return false;
253
254 m_loopCount = loopCount;
255 if (m_playing)
256 setLoopsRemaining(loopCount);
257 return true;
258}
259
260int QSoundEffectPrivateSynchronous::loopsRemaining() const
261{
262 return m_runningCount;
263}
264
265float QSoundEffectPrivateSynchronous::volume() const
266{
267 if (m_audioSink && !m_muted)
268 return m_audioSink->volume();
269
270 return m_volume;
271}
272
273bool QSoundEffectPrivateSynchronous::setVolume(float volume)
274{
275 volume = qBound(0.0f, volume, 1.0f);
276 if (m_volume == volume)
277 return false;
278
279 m_volume = volume;
280
281 if (m_audioSink && !m_muted)
282 m_audioSink->setVolume(volume);
283 return true;
284}
285
286bool QSoundEffectPrivateSynchronous::muted() const
287{
288 return m_muted;
289}
290
291bool QSoundEffectPrivateSynchronous::setMuted(bool muted)
292{
293 if (m_muted == muted)
294 return false;
295
296 if (muted && m_audioSink)
297 m_audioSink->setVolume(0);
298 else if (!muted && m_audioSink && m_muted)
299 m_audioSink->setVolume(m_volume);
300
301 m_muted = muted;
302 return true;
303}
304
305void QSoundEffectPrivateSynchronous::play()
306{
307 m_offset = 0;
308 setLoopsRemaining(m_loopCount);
309 qCDebug(qLcSoundEffect) << this << "play" << m_loopCount << m_runningCount;
310 if (m_status == QSoundEffect::Null || m_status == QSoundEffect::Error) {
311 setStatus(QSoundEffect::Null);
312 return;
313 }
314 setPlaying(true);
315}
316
317void QSoundEffectPrivateSynchronous::stop()
318{
319 if (!m_playing)
320 return;
321 qCDebug(qLcSoundEffect) << "stop()";
322 m_offset = 0;
323 setPlaying(false);
324}
325
326bool QSoundEffectPrivateSynchronous::playing() const
327{
328 return m_playing;
329}
330
331bool QSoundEffectPrivateSynchronous::setAudioDevice(QAudioDevice device)
332{
333 if (m_audioDevice == device)
334 return false;
335
336 m_audioDevice = device;
337
338 if (!m_sampleReady)
339 return true; // The audio sink will be recreated later by QSoundEffect::sampleReady()
340
341 bool playing = m_playing;
342 std::chrono::microseconds current_time{ m_audioBuffer.format().durationForBytes(m_offset) };
343
344 // Recreate the QAudioSink with the new audio device and current sample
345 if (updateAudioOutput() && playing) {
346 // Resume playback from current position
347 m_offset = m_audioBuffer.format().bytesForDuration(current_time.count());
348 setPlaying(true);
349 }
350 return true;
351}
352
353bool QSoundEffectPrivateSynchronous::setSource(const QUrl &url, QSampleCache &sampleCache)
354{
355 if (m_sampleLoadFuture.isValid())
356 m_sampleLoadFuture.cancel();
357
358 m_url = url;
359 m_sampleReady = false;
360
361 if (url.isEmpty()) {
362 setStatus(QSoundEffect::Null);
363 return false;
364 }
365
366 if (!url.isValid()) {
367 setStatus(QSoundEffect::Error);
368 return false;
369 }
370
371 setStatus(QSoundEffect::Loading);
372 m_sample = {};
373
374 if (m_audioSink) {
375 QObject::disconnect(m_audioSink.get(), &QAudioSink::stateChanged, this,
376 &QSoundEffectPrivateSynchronous::stateChanged);
377 m_audioSink.reset();
378 }
379
380 m_sampleLoadFuture =
381 sampleCache.requestSampleFuture(url).then(this, [this](SharedSamplePtr result) {
382 if (result)
383 sampleReady(std::move(result));
384 else
385 decoderError();
386 });
387
388 return true;
389}
390
391QSoundEffect::Status QSoundEffectPrivateSynchronous::status() const
392{
393 return m_status;
394}
395
396QUrl QSoundEffectPrivateSynchronous::url() const
397{
398 return m_url;
399}
400
401QAudioDevice QSoundEffectPrivateSynchronous::audioDevice() const
402{
403 return m_audioDevice;
404}
405
406void QSoundEffectPrivateSynchronous::stateChanged(QAudio::State state)
407{
408 qCDebug(qLcSoundEffect) << this << "stateChanged " << state;
409 if ((state == QAudio::IdleState && m_runningCount == 0) || state == QAudio::StoppedState)
410 stop();
411}
412
413QT_END_NAMESPACE