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
qsoundeffectwithplayer.cpp
Go to the documentation of this file.
1// Copyright (C) 2025 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 <QtCore/q20map.h>
7#include <QtCore/qmutex.h>
8
9#include <utility>
10
11QT_BEGIN_NAMESPACE
12
13namespace QtMultimediaPrivate {
14
15namespace {
16
17QSpan<const float> toFloatSpan(QSpan<const char> byteArray)
18{
19 return QSpan{
20 reinterpret_cast<const float *>(byteArray.data()),
21 qsizetype(byteArray.size_bytes() / sizeof(float)),
22 };
23}
24
25struct AudioDeviceFormatLess
26{
27 bool operator()(const std::pair<QAudioDevice, QAudioFormat> &lhs,
28 const std::pair<QAudioDevice, QAudioFormat> &rhs) const
29 {
30 auto cmp = qCompareThreeWay(lhs.first.id(), rhs.first.id());
31 if (cmp == Qt::strong_ordering::less)
32 return true;
33 if (cmp == Qt::strong_ordering::greater)
34 return false;
35
36 return std::tuple(lhs.second.sampleRate(), lhs.second.sampleFormat(),
37 lhs.second.channelCount())
38 < std::tuple(rhs.second.sampleRate(), rhs.second.sampleFormat(),
39 rhs.second.channelCount());
40 }
41};
42
43} // namespace
44
45// we keep a pool of engines with one engine per device/format
48{
49 if (device.isNull()) {
50 qWarning() << "QRtAudioEngine needs to be called with a valid device";
51 return nullptr;
52 }
53
55 qWarning() << "QRtAudioEngine requires floating point samples";
56 return nullptr;
57 }
58
60 qWarning() << "QRtAudioEngine needs to be called with a supported fromat";
61 return nullptr;
62 }
63
68
70
71 auto key = std::pair{ device, format };
73 if (found != s_playerRegistry.end()) {
74 auto player = found->second.lock();
75 if (player)
76 return player;
77 }
78
79 // lazy clean up
82 });
83
84 // SoundEffect can prevent the stream from appear in an OS mixer
86
90 delete engine;
91 else
93 });
95
96 return player;
97}
98
99///////////////////////////////////////////////////////////////////////////////////////////////////
100
101QSoundEffectVoice::QSoundEffectVoice(VoiceId voiceId, std::shared_ptr<const QSample> sample,
102 float volume, bool muted, int totalLoopCount,
103 QAudioFormat engineFormat)
104 : QRtAudioEngineVoice{ voiceId },
105 m_sample{ std::move(sample) },
106 m_engineFormat{ engineFormat },
107 m_volume{ volume },
108 m_muted{ muted },
109 m_loopsRemaining{ totalLoopCount }
110{
111}
112
114
139
141{
145
148
150
151 const int sampleCh = format.channelCount();
157
159 const ConversionType conversion = [&] {
160 if (sampleCh == engineCh)
161 return SameChannels;
162 if (sampleCh == 1 && engineCh == 2)
163 return MonoToStereo;
164 if (sampleCh == 2 && engineCh == 1)
165 return StereoToMono;
167 }();
168
169 if (m_muted || m_volume == 0.f)
170 return framesToPlay;
171
172 // later: (auto)vectorize?
173 switch (conversion) {
174 case SameChannels:
175 for (qsizetype frame = 0; frame < framesToPlay; ++frame) {
178 for (int ch = 0; ch < sampleCh; ++ch) {
180 }
181 }
182 break;
183 case MonoToStereo:
184 for (qsizetype frame = 0; frame < framesToPlay; ++frame) {
187 const float val = playbackRange[sampleBase] * m_volume;
190 }
191 break;
192 case StereoToMono:
193 float scale = 0.5f * m_volume;
194 for (qsizetype frame = 0; frame < framesToPlay; ++frame) {
197 const float val = (playbackRange[sampleBase] + playbackRange[sampleBase + 1]) * scale;
199 }
200 break;
201 }
202
203 return framesToPlay;
204}
205
207{
209 return true;
210
211 return loopsRemaining() != 0;
212}
213
216{
220
221 // caveat: reading frame is not atomic, so we may have a race here ... is is rare, though,
222 // not sure if we really care
224 return clone;
225}
226
227///////////////////////////////////////////////////////////////////////////////////////////////////
228
248
255
257{
258 if (device == m_audioDevice)
259 return false;
260
263 return true;
264}
265
267{
269 return;
270
272
273 if (m_player)
274 for (const auto &voice : m_voices)
276
279 };
280 m_voices.clear();
281
282 if (m_sample) {
284 if (!hasPlayer) {
286 return;
287 }
288
289 for (const auto &voice : voices)
290 // we re-allocate a new voice ID and play on the new player
292
294 } else {
296 }
297}
298
300{
301 if (m_audioDevice.isNull())
304}
305
310
312{
313 if (m_sampleLoadFuture) {
316 }
317
318 if (m_player) {
321 // we keep the player referenced for a little longer, so that later calls to
322 // getEngineFor will be able to reuse the existing instance
325 }
326
327 m_sample = {};
328
329 if (url.isEmpty()) {
331 return;
332 }
333
334 if (!url.isValid()) {
336 return;
337 }
338
340
343 if (result) {
345 qWarning("QSoundEffect: QSoundEffect only supports mono or stereo files");
347 return;
348 }
349
352 if (!hasPlayer) {
353 qWarning("QSoundEffect: playback of this format is not supported on the selected "
354 "audio device");
356 return;
357 }
358
360 if (std::exchange(m_playPending, false)) {
361 play();
362 }
363 } else {
364 qWarning("QSoundEffect: Error decoding source %ls", qUtf16Printable(url.toString()));
366 }
367 });
368}
369
371{
372 if (status == m_status)
373 return;
376}
377
382
384{
385 return m_loopCount;
386}
387
389{
390 if (loopCount == 0)
391 loopCount = 1;
392
393 if (loopCount == m_loopCount)
394 return false;
395
397
398 if (m_voices.empty())
399 return true;
400
403
405
406 return true;
407}
408
410{
411 if (m_voices.empty())
412 return 0;
413
414 return m_loopsRemaining;
415}
416
418{
419 return m_volume;
420}
421
423{
424 if (m_volume == volume)
425 return false;
426
428 for (const auto &voice : m_voices) {
431 });
432 }
433 return true;
434}
435
437{
438 return m_muted;
439}
440
442{
443 if (m_muted == muted)
444 return false;
445
446 m_muted = muted;
447 for (const auto &voice : m_voices) {
450 });
451 }
452 return true;
453}
454
456{
457 if (!m_sample) {
458 m_playPending = true;
459 return;
460 }
461
462 if (status() != QSoundEffect::Ready)
463 return;
464
466
467 // each `play` will start a new voice
471
472 play(std::move(voice));
473}
474
476{
478 for (const auto &voice : m_voices)
481
482 m_voices.clear();
483 m_playPending = false;
484 if (activeVoices)
486}
487
489{
490 return !m_voices.empty();
491}
492
494{
496 [this, voiceId = voice->voiceId()] {
498 if (foundVoice == m_voices.end())
499 return;
500
501 if (voiceId != activeVoice())
502 return;
503
505 });
506
510 if (m_voices.size() == 1)
512}
513
515{
519
520 m_player = {};
522 return false;
523
526 if (player)
527 return player;
528
530 switch (sample->format().channelCount()) {
531 case 1:
533 break;
534 case 2:
536 break;
537 default:
539 }
540
542 }();
543
544 if (!m_player)
545 return false;
546
548 this, [this](VoiceId voiceId) {
549 if (voiceId == activeVoice())
551
552 auto found = m_voices.find(voiceId);
553 if (found != m_voices.end()) {
555 if (m_voices.empty())
557 }
558 });
559 return true;
560}
561
563{
564 if (m_voices.empty())
565 return std::nullopt;
566 return (*m_voices.rbegin())->voiceId();
567}
568
570{
571 switch (fmt.channelCount()) {
572 case 1:
573 case 2:
574 return true;
575 default:
576 return false;
577 }
578}
579
581{
583 return;
586}
587
588} // namespace QtMultimediaPrivate
589
590QT_END_NAMESPACE
Q_MULTIMEDIA_EXPORT ~QSoundEffectVoice()