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#include <QtCore/private/qobject_p.h>
9
10#include <utility>
11
12QT_BEGIN_NAMESPACE
13
14namespace QtMultimediaPrivate {
15
16namespace {
17
18QSpan<const float> toFloatSpan(QSpan<const char> byteArray)
19{
20 return QSpan{
21 reinterpret_cast<const float *>(byteArray.data()),
22 qsizetype(byteArray.size_bytes() / sizeof(float)),
23 };
24}
25
26struct AudioDeviceFormatLess
27{
28 bool operator()(const std::pair<QAudioDevice, QAudioFormat> &lhs,
29 const std::pair<QAudioDevice, QAudioFormat> &rhs) const
30 {
31 auto cmp = qCompareThreeWay(lhs.first.id(), rhs.first.id());
32 if (cmp == Qt::strong_ordering::less)
33 return true;
34 if (cmp == Qt::strong_ordering::greater)
35 return false;
36
37 return std::tuple(lhs.second.sampleRate(), lhs.second.sampleFormat(),
38 lhs.second.channelCount())
39 < std::tuple(rhs.second.sampleRate(), rhs.second.sampleFormat(),
40 rhs.second.channelCount());
41 }
42};
43
44} // namespace
45
46// we keep a pool of engines with one engine per device/format
49{
50 if (device.isNull()) {
51 qWarning() << "QRtAudioEngine needs to be called with a valid device";
52 return nullptr;
53 }
54
56 qWarning() << "QRtAudioEngine requires floating point samples";
57 return nullptr;
58 }
59
61 qWarning() << "QRtAudioEngine needs to be called with a supported fromat";
62 return nullptr;
63 }
64
69
71
72 auto key = std::pair{ device, format };
74 if (found != s_playerRegistry.end()) {
75 auto player = found->second.lock();
76 if (player)
77 return player;
78 }
79
80 // lazy clean up
83 });
84
85 // SoundEffect can prevent the stream from appear in an OS mixer
87
93
94 return player;
95}
96
97///////////////////////////////////////////////////////////////////////////////////////////////////
98
99QSoundEffectVoice::QSoundEffectVoice(VoiceId voiceId, std::shared_ptr<const QSample> sample,
100 float volume, bool muted, int totalLoopCount,
101 QAudioFormat engineFormat)
102 : QRtAudioEngineVoice{ voiceId },
103 m_sample{ std::move(sample) },
104 m_engineFormat{ engineFormat },
105 m_volume{ volume },
106 m_muted{ muted },
107 m_loopsRemaining{ totalLoopCount }
108{
109}
110
112
137
139{
143
146
148
149 const int sampleCh = format.channelCount();
155
157 const ConversionType conversion = [&] {
158 if (sampleCh == engineCh)
159 return SameChannels;
160 if (sampleCh == 1 && engineCh == 2)
161 return MonoToStereo;
162 if (sampleCh == 2 && engineCh == 1)
163 return StereoToMono;
165 }();
166
167 if (m_muted || m_volume == 0.f)
168 return framesToPlay;
169
170 // later: (auto)vectorize?
171 switch (conversion) {
172 case SameChannels:
173 for (qsizetype frame = 0; frame < framesToPlay; ++frame) {
176 for (int ch = 0; ch < sampleCh; ++ch) {
178 }
179 }
180 break;
181 case MonoToStereo:
182 for (qsizetype frame = 0; frame < framesToPlay; ++frame) {
185 const float val = playbackRange[sampleBase] * m_volume;
188 }
189 break;
190 case StereoToMono:
191 float scale = 0.5f * m_volume;
192 for (qsizetype frame = 0; frame < framesToPlay; ++frame) {
195 const float val = (playbackRange[sampleBase] + playbackRange[sampleBase + 1]) * scale;
197 }
198 break;
199 }
200
201 return framesToPlay;
202}
203
205{
207 return true;
208
209 return loopsRemaining() != 0;
210}
211
214{
218
219 // caveat: reading frame is not atomic, so we may have a race here ... is is rare, though,
220 // not sure if we really care
222 return clone;
223}
224
225///////////////////////////////////////////////////////////////////////////////////////////////////
226
246
253
255{
256 if (device == m_audioDevice)
257 return false;
258
261 return true;
262}
263
265{
267 return;
268
270
271 if (m_player)
272 for (const auto &voice : m_voices)
274
277 };
278 m_voices.clear();
279
280 if (m_sample) {
282 if (!hasPlayer) {
284 return;
285 }
286
287 for (const auto &voice : voices)
288 // we re-allocate a new voice ID and play on the new player
290
292 } else {
294 }
295}
296
298{
299 if (m_audioDevice.isNull())
302}
303
308
310{
311 if (m_sampleLoadFuture) {
314 }
315
316 if (m_player) {
319 // we keep the player referenced for a little longer, so that later calls to
320 // getEngineFor will be able to reuse the existing instance
323 }
324
325 m_sample = {};
326
327 if (url.isEmpty()) {
329 return;
330 }
331
332 if (!url.isValid()) {
334 return;
335 }
336
338
341 if (result) {
343 qWarning("QSoundEffect: QSoundEffect only supports mono or stereo files");
345 return;
346 }
347
350 if (!hasPlayer) {
351 qWarning("QSoundEffect: playback of this format is not supported on the selected "
352 "audio device");
354 return;
355 }
356
358 if (std::exchange(m_playPending, false)) {
359 play();
360 }
361 } else {
362 qWarning("QSoundEffect: Error decoding source %ls", qUtf16Printable(url.toString()));
364 }
365 });
366}
367
369{
370 if (status == m_status)
371 return;
374}
375
380
382{
383 return m_loopCount;
384}
385
387{
388 if (loopCount == 0)
389 loopCount = 1;
390
391 if (loopCount == m_loopCount)
392 return false;
393
395
396 if (m_voices.empty())
397 return true;
398
401
403
404 return true;
405}
406
408{
409 if (m_voices.empty())
410 return 0;
411
412 return m_loopsRemaining;
413}
414
416{
417 return m_volume;
418}
419
421{
422 if (m_volume == volume)
423 return false;
424
426 for (const auto &voice : m_voices) {
429 });
430 }
431 return true;
432}
433
435{
436 return m_muted;
437}
438
440{
441 if (m_muted == muted)
442 return false;
443
444 m_muted = muted;
445 for (const auto &voice : m_voices) {
448 });
449 }
450 return true;
451}
452
454{
455 if (!m_sample) {
456 m_playPending = true;
457 return;
458 }
459
460 if (status() != QSoundEffect::Ready)
461 return;
462
464
465 // each `play` will start a new voice
469
470 play(std::move(voice));
471}
472
474{
476 for (const auto &voice : m_voices)
479
480 m_voices.clear();
481 m_playPending = false;
482 if (activeVoices)
484}
485
487{
488 return !m_voices.empty();
489}
490
492{
494 [this, voiceId = voice->voiceId()] {
496 if (foundVoice == m_voices.end())
497 return;
498
499 if (voiceId != activeVoice())
500 return;
501
503 });
504
508 if (m_voices.size() == 1)
510}
511
513{
517
518 m_player = {};
520 return false;
521
524 if (player)
525 return player;
526
528 switch (sample->format().channelCount()) {
529 case 1:
531 break;
532 case 2:
534 break;
535 default:
537 }
538
540 }();
541
542 if (!m_player)
543 return false;
544
546 this, [this](VoiceId voiceId) {
547 if (voiceId == activeVoice())
549
550 auto found = m_voices.find(voiceId);
551 if (found != m_voices.end()) {
553 if (m_voices.empty())
555 }
556 });
557 return true;
558}
559
561{
562 if (m_voices.empty())
563 return std::nullopt;
564 return (*m_voices.rbegin())->voiceId();
565}
566
568{
569 switch (fmt.channelCount()) {
570 case 1:
571 case 2:
572 return true;
573 default:
574 return false;
575 }
576}
577
579{
581 return;
584}
585
586} // namespace QtMultimediaPrivate
587
588QT_END_NAMESPACE
Q_MULTIMEDIA_EXPORT ~QSoundEffectVoice()