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
4
#
include
"qsoundeffectwithplayer_p.h"
5
6
#
include
<
QtCore
/
qmutex
.
h
>
7
#
include
<
QtCore
/
q20map
.
h
>
8
9
#
include
<
utility
>
10
11
QT_BEGIN_NAMESPACE
12
13
namespace
QtMultimediaPrivate
{
14
15
namespace
{
16
17
QSpan<
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
25
}
// namespace
26
27
///////////////////////////////////////////////////////////////////////////////////////////////////
28
29
QSoundEffectVoice
::QSoundEffectVoice(VoiceId voiceId, std::shared_ptr<
const
QSample> sample,
30
float
volume,
bool
muted,
int
totalLoopCount,
31
QAudioFormat engineFormat)
32
: QRtAudioEngineVoice{ voiceId },
33
m_sample{ std::move(sample) },
34
m_engineFormat{ engineFormat },
35
m_volume
{ volume },
36
m_muted
{ muted },
37
m_loopsRemaining{ totalLoopCount }
38
{
39
}
40
41
QSoundEffectVoice
::~
QSoundEffectVoice
() =
default
;
42
43
VoicePlayResult
QSoundEffectVoice
::
play
(
QSpan
<
float
>
outputBuffer
)
noexcept
QT_MM_NONBLOCKING
44
{
45
qsizetype
playedFrames
=
playVoice
(
outputBuffer
);
46
m_currentFrame
+=
playedFrames
;
47
48
if
(
m_currentFrame
==
m_totalFrames
) {
49
const
bool
isInfiniteLoop
=
loopsRemaining
() ==
QSoundEffect
::
Infinite
;
50
bool
continuePlaying
=
isInfiniteLoop
;
51
52
if
(!
isInfiniteLoop
)
53
continuePlaying
=
m_loopsRemaining
.
fetch_sub
(1,
std
::
memory_order_relaxed
) > 1;
54
55
if
(
continuePlaying
) {
56
if
(!
isInfiniteLoop
)
57
m_currentLoopChanged
.
set
();
58
m_currentFrame
= 0;
59
QSpan
remainingOutputBuffer
=
60
drop
(
outputBuffer
,
playedFrames
*
m_engineFormat
.
channelCount
());
61
return
play
(
remainingOutputBuffer
);
62
}
63
return
VoicePlayResult
::
Finished
;
64
}
65
return
VoicePlayResult
::
Playing
;
66
}
67
68
qsizetype
QSoundEffectVoice
::
playVoice
(
QSpan
<
float
>
outputBuffer
)
noexcept
QT_MM_NONBLOCKING
69
{
70
const
QAudioFormat
&
format
=
m_sample
->
format
();
71
const
int
totalSamples
=
m_totalFrames
*
format
.
channelCount
();
72
const
int
currentSample
=
format
.
channelCount
() *
m_currentFrame
;
73
74
const
QSpan
fullSample
=
toFloatSpan
(
m_sample
->
data
());
75
const
QSpan
playbackRange
=
take
(
drop
(
fullSample
,
currentSample
),
totalSamples
);
76
77
Q_ASSERT
(!
playbackRange
.
empty
());
78
79
const
int
sampleCh
=
format
.
channelCount
();
80
const
int
engineCh
=
m_engineFormat
.
channelCount
();
81
const
qsizetype
sampleSamples
=
playbackRange
.
size
();
82
const
qsizetype
outputSamples
=
outputBuffer
.
size
();
83
const
qsizetype
maxFrames
=
std
::
min
(
sampleSamples
/
sampleCh
,
outputSamples
/
engineCh
);
84
const
qsizetype
framesToPlay
=
maxFrames
;
85
const
qsizetype
outputSamplesPlayed
=
framesToPlay
*
engineCh
;
86
87
enum
ConversionType
:
uint8_t
{
SameChannels
,
MonoToStereo
,
StereoToMono
};
88
const
ConversionType
conversion
= [&] {
89
if
(
sampleCh
==
engineCh
)
90
return
SameChannels
;
91
if
(
sampleCh
== 1 &&
engineCh
== 2)
92
return
MonoToStereo
;
93
if
(
sampleCh
== 2 &&
engineCh
== 1)
94
return
StereoToMono
;
95
Q_UNREACHABLE_RETURN
(
SameChannels
);
96
}();
97
98
if
(
m_muted
||
m_volume
== 0.f) {
99
std
::
fill_n
(
outputBuffer
.
begin
(),
outputSamplesPlayed
, 0.f);
100
return
framesToPlay
;
101
}
102
103
// later: (auto)vectorize?
104
switch
(
conversion
) {
105
case
SameChannels
:
106
for
(
qsizetype
frame
= 0;
frame
<
framesToPlay
; ++
frame
) {
107
const
qsizetype
sampleBase
=
frame
*
sampleCh
;
108
const
qsizetype
outputBase
=
frame
*
engineCh
;
109
for
(
int
ch
= 0;
ch
<
sampleCh
; ++
ch
) {
110
outputBuffer
[
outputBase
+
ch
] +=
playbackRange
[
sampleBase
+
ch
] *
m_volume
;
111
}
112
}
113
break
;
114
case
MonoToStereo
:
115
for
(
qsizetype
frame
= 0;
frame
<
framesToPlay
; ++
frame
) {
116
const
qsizetype
sampleBase
=
frame
*
sampleCh
;
117
const
qsizetype
outputBase
=
frame
*
engineCh
;
118
const
float
val
=
playbackRange
[
sampleBase
] *
m_volume
;
119
outputBuffer
[
outputBase
] +=
val
;
120
outputBuffer
[
outputBase
+ 1] +=
val
;
121
}
122
break
;
123
case
StereoToMono
:
124
float
scale
= 0.5f *
m_volume
;
125
for
(
qsizetype
frame
= 0;
frame
<
framesToPlay
; ++
frame
) {
126
const
qsizetype
sampleBase
=
frame
*
sampleCh
;
127
const
qsizetype
outputBase
=
frame
*
engineCh
;
128
const
float
val
= (
playbackRange
[
sampleBase
] +
playbackRange
[
sampleBase
+ 1]) *
scale
;
129
outputBuffer
[
outputBase
] +=
val
;
130
}
131
break
;
132
}
133
134
return
framesToPlay
;
135
}
136
137
bool
QSoundEffectVoice
::
isActive
()
noexcept
QT_MM_NONBLOCKING
138
{
139
if
(
m_currentFrame
!=
m_totalFrames
)
140
return
true
;
141
142
return
loopsRemaining
() != 0;
143
}
144
145
std
::
shared_ptr
<
QSoundEffectVoice
>
146
QSoundEffectVoice
::
clone
(
std
::
optional
<
QAudioFormat
>
newEngineFormat
)
const
147
{
148
auto
clone
=
std
::
make_shared
<
QSoundEffectVoice
>(
QRtAudioEngine
::
allocateVoiceId
(),
m_sample
,
149
m_volume
,
m_muted
,
loopsRemaining
(),
150
newEngineFormat
.
value_or
(
m_engineFormat
));
151
152
// caveat: reading frame is not atomic, so we may have a race here ... is is rare, though,
153
// not sure if we really care
154
clone
->
m_currentFrame
=
m_currentFrame
;
155
return
clone
;
156
}
157
158
///////////////////////////////////////////////////////////////////////////////////////////////////
159
160
QSoundEffectPrivateWithPlayer
::
QSoundEffectPrivateWithPlayer
(
QSoundEffect
*
q
,
161
QAudioDevice
audioDevice
)
162
:
q_ptr
{
q
},
m_audioDevice
{
std
::
move
(
audioDevice
) }
163
{
164
resolveAudioDevice
();
165
166
QObject
::
connect
(&
m_mediaDevices
, &
QMediaDevices
::
audioOutputsChanged
,
this
, [
this
] {
167
QAudioDevice
defaultAudioDevice
=
QMediaDevices
::
defaultAudioOutput
();
168
if
(
defaultAudioDevice
==
m_defaultAudioDevice
)
169
return
;
170
171
m_defaultAudioDevice
=
QMediaDevices
::
defaultAudioOutput
();
172
if
(
m_audioDevice
.
isNull
())
173
setResolvedAudioDevice
(
m_defaultAudioDevice
);
174
});
175
176
m_playerReleaseTimer
.
setTimerType
(
Qt
::
VeryCoarseTimer
);
177
m_playerReleaseTimer
.
setSingleShot
(
true
);
178
}
179
180
QSoundEffectPrivateWithPlayer
::~
QSoundEffectPrivateWithPlayer
()
181
{
182
stop
();
183
if
(
m_sampleLoadFuture
)
184
m_sampleLoadFuture
->
cancelChain
();
185
}
186
187
bool
QSoundEffectPrivateWithPlayer
::
setAudioDevice
(
QAudioDevice
device
)
188
{
189
if
(
device
==
m_audioDevice
)
190
return
false
;
191
192
m_audioDevice
=
std
::
move
(
device
);
193
resolveAudioDevice
();
194
return
true
;
195
}
196
197
void
QSoundEffectPrivateWithPlayer
::
setResolvedAudioDevice
(
QAudioDevice
device
)
198
{
199
if
(
m_resolvedAudioDevice
==
device
)
200
return
;
201
202
m_resolvedAudioDevice
=
std
::
move
(
device
);
203
204
if
(
m_player
)
205
for
(
const
auto
&
voice
:
m_voices
)
206
m_player
->
stop
(
voice
);
207
208
std
::
vector
<
std
::
shared_ptr
<
QSoundEffectVoice
>>
voices
{
209
std
::
make_move_iterator
(
m_voices
.
begin
()),
std
::
make_move_iterator
(
m_voices
.
end
())
210
};
211
m_voices
.
clear
();
212
213
if
(
m_sample
) {
214
bool
hasPlayer
=
updatePlayer
(
m_sample
);
215
if
(!
hasPlayer
) {
216
setStatus
(
QSoundEffect
::
Error
);
217
return
;
218
}
219
220
for
(
const
auto
&
voice
:
voices
)
221
// we re-allocate a new voice ID and play on the new player
222
play
(
voice
->
clone
(
m_player
->
audioSink
().
format
()));
223
224
setStatus
(
QSoundEffect
::
Ready
);
225
226
for
(
const
auto
&
voice
:
voices
)
227
// we re-allocate a new voice ID and play on the new player
228
play
(
voice
->
clone
());
229
}
else
{
230
setStatus
(
m_sampleLoadFuture
?
QSoundEffect
::
Loading
:
QSoundEffect
::
Null
);
231
}
232
}
233
234
void
QSoundEffectPrivateWithPlayer
::
resolveAudioDevice
()
235
{
236
if
(
m_audioDevice
.
isNull
())
237
m_defaultAudioDevice
=
QMediaDevices
::
defaultAudioOutput
();
238
setResolvedAudioDevice
(
m_audioDevice
.
isNull
() ?
m_defaultAudioDevice
:
m_audioDevice
);
239
}
240
241
QAudioDevice
QSoundEffectPrivateWithPlayer
::
audioDevice
()
const
242
{
243
return
m_audioDevice
;
244
}
245
246
bool
QSoundEffectPrivateWithPlayer
::
setSource
(
const
QUrl
&
url
,
QSampleCache
&
sampleCache
)
247
{
248
if
(
m_sampleLoadFuture
) {
249
m_sampleLoadFuture
->
cancelChain
();
250
m_sampleLoadFuture
=
std
::
nullopt
;
251
}
252
253
if
(
m_player
) {
254
QObject
::
disconnect
(
m_voiceFinishedConnection
);
255
m_playerReleaseTimer
.
callOnTimeout
(
this
, [
player
=
std
::
move
(
m_player
)] {
256
// we keep the player referenced for a little longer, so that later calls to
257
// QRtAudioEngine::getEngineFor will be able to reuse the existing instance
258
},
Qt
::
SingleShotConnection
);
259
m_playerReleaseTimer
.
start
();
260
}
261
262
m_url
=
url
;
263
m_sample
= {};
264
265
if
(
url
.
isEmpty
()) {
266
setStatus
(
QSoundEffect
::
Null
);
267
return
false
;
268
}
269
270
if
(!
url
.
isValid
()) {
271
setStatus
(
QSoundEffect
::
Error
);
272
return
false
;
273
}
274
275
setStatus
(
QSoundEffect
::
Loading
);
276
277
m_sampleLoadFuture
=
278
sampleCache
.
requestSampleFuture
(
url
).
then
(
this
, [
this
](
SharedSamplePtr
result
) {
279
if
(
result
) {
280
if
(!
formatIsSupported
(
result
->
format
())) {
281
qWarning
(
"QSoundEffect: QSoundEffect only supports mono or stereo files"
);
282
setStatus
(
QSoundEffect
::
Error
);
283
return
;
284
}
285
286
bool
hasPlayer
=
updatePlayer
(
result
);
287
m_sample
=
std
::
move
(
result
);
288
if
(!
hasPlayer
) {
289
qWarning
(
"QSoundEffect: playback of this format is not supported on the selected "
290
"audio device"
);
291
setStatus
(
QSoundEffect
::
Error
);
292
return
;
293
}
294
295
setStatus
(
QSoundEffect
::
Ready
);
296
if
(
std
::
exchange
(
m_playPending
,
false
)) {
297
play
();
298
}
299
}
else
{
300
qWarning
(
"QSoundEffect: Error decoding source %ls"
,
qUtf16Printable
(
m_url
.
toString
()));
301
setStatus
(
QSoundEffect
::
Error
);
302
}
303
});
304
305
return
true
;
306
}
307
308
QUrl
QSoundEffectPrivateWithPlayer
::
url
()
const
309
{
310
return
m_url
;
311
}
312
313
void
QSoundEffectPrivateWithPlayer
::
setStatus
(
QSoundEffect
::
Status
status
)
314
{
315
if
(
status
==
m_status
)
316
return
;
317
m_status
=
status
;
318
emit
q_ptr
->
statusChanged
();
319
}
320
321
QSoundEffect
::
Status
QSoundEffectPrivateWithPlayer
::
status
()
const
322
{
323
return
m_status
;
324
}
325
326
int
QSoundEffectPrivateWithPlayer
::
loopCount
()
const
327
{
328
return
m_loopCount
;
329
}
330
331
bool
QSoundEffectPrivateWithPlayer
::
setLoopCount
(
int
loopCount
)
332
{
333
if
(
loopCount
== 0)
334
loopCount
= 1;
335
336
if
(
loopCount
==
m_loopCount
)
337
return
false
;
338
339
m_loopCount
=
loopCount
;
340
341
if
(
m_voices
.
empty
())
342
return
true
;
343
344
const
std
::
shared_ptr
<
QSoundEffectVoice
> &
voice
= *
m_voices
.
rbegin
();
345
voice
->
m_loopsRemaining
.
store
(
loopCount
,
std
::
memory_order_relaxed
);
346
347
setLoopsRemaining
(
loopCount
);
348
349
return
true
;
350
}
351
352
int
QSoundEffectPrivateWithPlayer
::
loopsRemaining
()
const
353
{
354
if
(
m_voices
.
empty
())
355
return
0;
356
357
return
m_loopsRemaining
;
358
}
359
360
float
QSoundEffectPrivateWithPlayer
::
volume
()
const
361
{
362
return
m_volume
;
363
}
364
365
bool
QSoundEffectPrivateWithPlayer
::
setVolume
(
float
volume
)
366
{
367
if
(
m_volume
==
volume
)
368
return
false
;
369
370
m_volume
=
volume
;
371
for
(
const
auto
&
voice
:
m_voices
) {
372
m_player
->
visitVoiceRt
(
voice
, [
volume
](
QSoundEffectVoice
&
voice
) {
373
voice
.
m_volume
=
volume
;
374
});
375
}
376
return
true
;
377
}
378
379
bool
QSoundEffectPrivateWithPlayer
::
muted
()
const
380
{
381
return
m_muted
;
382
}
383
384
bool
QSoundEffectPrivateWithPlayer
::
setMuted
(
bool
muted
)
385
{
386
if
(
m_muted
==
muted
)
387
return
false
;
388
389
m_muted
=
muted
;
390
for
(
const
auto
&
voice
:
m_voices
) {
391
m_player
->
visitVoiceRt
(
voice
, [
muted
](
QSoundEffectVoice
&
voice
) {
392
voice
.
m_muted
=
muted
;
393
});
394
}
395
return
true
;
396
}
397
398
void
QSoundEffectPrivateWithPlayer
::
play
()
399
{
400
if
(!
m_sample
) {
401
m_playPending
=
true
;
402
return
;
403
}
404
405
if
(
status
() !=
QSoundEffect
::
Ready
)
406
return
;
407
408
Q_ASSERT
(
m_player
);
409
410
// each `play` will start a new voice
411
auto
voice
=
std
::
make_shared
<
QSoundEffectVoice
>(
QRtAudioEngine
::
allocateVoiceId
(),
m_sample
,
412
m_volume
,
m_muted
,
m_loopCount
,
413
m_player
->
audioSink
().
format
());
414
415
play
(
std
::
move
(
voice
));
416
}
417
418
void
QSoundEffectPrivateWithPlayer
::
stop
()
419
{
420
size_t
activeVoices
=
m_voices
.
size
();
421
for
(
const
auto
&
voice
:
m_voices
)
422
m_player
->
stop
(
voice
->
voiceId
());
423
setLoopsRemaining
(0);
424
425
m_voices
.
clear
();
426
m_playPending
=
false
;
427
if
(
activeVoices
)
428
emit
q_ptr
->
playingChanged
();
429
}
430
431
bool
QSoundEffectPrivateWithPlayer
::
playing
()
const
432
{
433
return
!
m_voices
.
empty
();
434
}
435
436
void
QSoundEffectPrivateWithPlayer
::
play
(
std
::
shared_ptr
<
QSoundEffectVoice
>
voice
)
437
{
438
QObject
::
connect
(&
voice
->
m_currentLoopChanged
, &
QAutoResetEvent
::
activated
,
this
,
439
[
this
,
voiceId
=
voice
->
voiceId
()] {
440
auto
foundVoice
=
m_voices
.
find
(
voiceId
);
441
if
(
foundVoice
==
m_voices
.
end
())
442
return
;
443
444
if
(
voiceId
!=
activeVoice
())
445
return
;
446
447
setLoopsRemaining
((*
foundVoice
)->
loopsRemaining
());
448
});
449
450
m_player
->
play
(
voice
);
451
m_voices
.
insert
(
std
::
move
(
voice
));
452
setLoopsRemaining
(
m_loopCount
);
453
if
(
m_voices
.
size
() == 1)
454
emit
q_ptr
->
playingChanged
();
455
}
456
457
bool
QSoundEffectPrivateWithPlayer
::
updatePlayer
(
const
SharedSamplePtr
&
sample
)
458
{
459
Q_ASSERT
(
sample
);
460
Q_ASSERT
(
m_voices
.
empty
());
461
QObject
::
disconnect
(
m_voiceFinishedConnection
);
462
463
m_player
= {};
464
if
(
m_resolvedAudioDevice
.
isNull
())
465
return
false
;
466
467
m_player
= [&]() ->
std
::
shared_ptr
<
QRtAudioEngine
> {
468
auto
player
=
QRtAudioEngine
::
getEngineFor
(
m_resolvedAudioDevice
,
sample
->
format
());
469
if
(
player
)
470
return
player
;
471
472
QAudioFormat
alternativeFormat
=
sample
->
format
();
473
switch
(
sample
->
format
().
channelCount
()) {
474
case
1:
475
alternativeFormat
.
setChannelCount
(2);
476
break
;
477
case
2:
478
alternativeFormat
.
setChannelCount
(1);
479
break
;
480
default
:
481
Q_UNREACHABLE_RETURN
({});
482
}
483
484
return
QRtAudioEngine
::
getEngineFor
(
m_resolvedAudioDevice
,
alternativeFormat
);
485
}();
486
487
if
(!
m_player
)
488
return
false
;
489
490
m_voiceFinishedConnection
=
QObject
::
connect
(
m_player
.
get
(), &
QRtAudioEngine
::
voiceFinished
,
491
this
, [
this
](
VoiceId
voiceId
) {
492
if
(
voiceId
==
activeVoice
())
493
setLoopsRemaining
(0);
494
495
auto
found
=
m_voices
.
find
(
voiceId
);
496
if
(
found
!=
m_voices
.
end
()) {
497
m_voices
.
erase
(
found
);
498
if
(
m_voices
.
empty
())
499
emit
q_ptr
->
playingChanged
();
500
}
501
});
502
return
true
;
503
}
504
505
std
::
optional
<
VoiceId
>
QSoundEffectPrivateWithPlayer
::
activeVoice
()
const
506
{
507
if
(
m_voices
.
empty
())
508
return
std
::
nullopt
;
509
return
(*
m_voices
.
rbegin
())->
voiceId
();
510
}
511
512
bool
QSoundEffectPrivateWithPlayer
::
formatIsSupported
(
const
QAudioFormat
&
fmt
)
513
{
514
switch
(
fmt
.
channelCount
()) {
515
case
1:
516
case
2:
517
return
true
;
518
default
:
519
return
false
;
520
}
521
}
522
523
void
QSoundEffectPrivateWithPlayer
::
setLoopsRemaining
(
int
loopsRemaining
)
524
{
525
if
(
loopsRemaining
==
m_loopsRemaining
)
526
return
;
527
m_loopsRemaining
=
loopsRemaining
;
528
emit
q_ptr
->
loopsRemainingChanged
();
529
}
530
531
}
// namespace QtMultimediaPrivate
532
533
QT_END_NAMESPACE
QtMultimediaPrivate::QSoundEffectVoice::~QSoundEffectVoice
Q_MULTIMEDIA_EXPORT ~QSoundEffectVoice()
QtMultimediaPrivate::QSoundEffectVoice::m_volume
float m_volume
Definition
qsoundeffectwithplayer_p.h:59
QtMultimediaPrivate::QSoundEffectVoice::m_muted
bool m_muted
Definition
qsoundeffectwithplayer_p.h:60
QtMultimediaPrivate::QSoundEffectVoice::m_totalFrames
const int m_totalFrames
Definition
qsoundeffectwithplayer_p.h:53
QtMultimediaPrivate
Definition
qaudiosystem_p.h:51
qtmultimedia
src
multimedia
audio
qsoundeffectwithplayer.cpp
Generated on
for Qt by
1.16.1