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
:
QRtAudioEngineVoice
{
voiceId
},
32
m_sample
{
std
::
move
(
sample
) },
33
m_volume
{ volume },
34
m_muted
{ muted },
35
m_loopsRemaining
{
totalLoopCount
}
36
{
37
}
38
39
VoicePlayResult
QSoundEffectVoice
::
play
(
QSpan
<
float
>
outputBuffer
)
noexcept
QT_MM_NONBLOCKING
40
{
41
const
QAudioFormat
&
format
=
m_sample
->
format
();
42
int
totalSamples
=
m_totalFrames
*
format
.
channelCount
();
43
int
currentSample
=
format
.
channelCount
() *
m_currentFrame
;
44
45
const
QSpan
fullSample
=
toFloatSpan
(
m_sample
->
data
());
46
QSpan
playbackRange
=
take
(
drop
(
fullSample
,
currentSample
),
totalSamples
);
47
48
Q_ASSERT
(!
playbackRange
.
empty
());
49
50
// later: (auto)vectorize?
51
qsizetype
samplesToPlay
=
std
::
min
(
playbackRange
.
size
(),
outputBuffer
.
size
());
52
if
(
m_muted
||
m_volume
== 0.f) {
53
auto
outputRange
=
take
(
outputBuffer
,
samplesToPlay
);
54
std
::
fill
(
outputRange
.
begin
(),
outputRange
.
end
(), 0.f);
55
}
else
if
(
m_volume
== 1.f) {
56
for
(
qsizetype
i
= 0;
i
!=
samplesToPlay
; ++
i
)
57
outputBuffer
[
i
] +=
playbackRange
[
i
];
58
}
else
{
59
for
(
qsizetype
i
= 0;
i
!=
samplesToPlay
; ++
i
)
60
outputBuffer
[
i
] +=
playbackRange
[
i
] *
m_volume
;
61
}
62
63
m_currentFrame
+=
samplesToPlay
/
format
.
channelCount
();
64
65
if
(
m_currentFrame
==
m_totalFrames
) {
66
const
bool
isInfiniteLoop
=
loopsRemaining
() ==
QSoundEffect
::
Infinite
;
67
bool
continuePlaying
=
isInfiniteLoop
;
68
69
if
(!
isInfiniteLoop
)
70
continuePlaying
=
m_loopsRemaining
.
fetch_sub
(1,
std
::
memory_order_relaxed
) > 1;
71
72
if
(
continuePlaying
) {
73
if
(!
isInfiniteLoop
)
74
m_currentLoopChanged
.
set
();
75
m_currentFrame
= 0;
76
QSpan
remainingOutputBuffer
=
drop
(
outputBuffer
,
samplesToPlay
);
77
return
play
(
remainingOutputBuffer
);
78
}
79
return
VoicePlayResult
::
Finished
;
80
}
81
return
VoicePlayResult
::
Playing
;
82
}
83
84
bool
QSoundEffectVoice
::
isActive
()
noexcept
QT_MM_NONBLOCKING
85
{
86
if
(
m_currentFrame
!=
m_totalFrames
)
87
return
true
;
88
89
return
loopsRemaining
() != 0;
90
}
91
92
std
::
shared_ptr
<
QSoundEffectVoice
>
QSoundEffectVoice
::
clone
()
const
93
{
94
auto
clone
=
std
::
make_shared
<
QSoundEffectVoice
>(
QRtAudioEngine
::
allocateVoiceId
(),
m_sample
,
95
m_volume
,
m_muted
,
loopsRemaining
());
96
97
// caveat: reading frame is not atomic, so we may have a race here ... is is rare, though,
98
// not sure if we really care
99
clone
->
m_currentFrame
=
m_currentFrame
;
100
return
clone
;
101
}
102
103
///////////////////////////////////////////////////////////////////////////////////////////////////
104
105
QSoundEffectPrivateWithPlayer
::
QSoundEffectPrivateWithPlayer
(
QSoundEffect
*
q
,
106
QAudioDevice
audioDevice
)
107
:
q_ptr
{
q
},
m_audioDevice
{
std
::
move
(
audioDevice
) }
108
{
109
resolveAudioDevice
();
110
111
QObject
::
connect
(&
m_mediaDevices
, &
QMediaDevices
::
audioOutputsChanged
,
this
, [
this
] {
112
QAudioDevice
defaultAudioDevice
=
QMediaDevices
::
defaultAudioOutput
();
113
if
(
defaultAudioDevice
==
m_defaultAudioDevice
)
114
return
;
115
116
m_defaultAudioDevice
=
QMediaDevices
::
defaultAudioOutput
();
117
if
(
m_audioDevice
.
isNull
())
118
setResolvedAudioDevice
(
m_defaultAudioDevice
);
119
});
120
}
121
122
QSoundEffectPrivateWithPlayer
::~
QSoundEffectPrivateWithPlayer
()
123
{
124
stop
();
125
}
126
127
bool
QSoundEffectPrivateWithPlayer
::
setAudioDevice
(
QAudioDevice
device
)
128
{
129
if
(
device
==
m_audioDevice
)
130
return
false
;
131
132
m_audioDevice
=
std
::
move
(
device
);
133
resolveAudioDevice
();
134
return
true
;
135
}
136
137
void
QSoundEffectPrivateWithPlayer
::
setResolvedAudioDevice
(
QAudioDevice
device
)
138
{
139
if
(
m_resolvedAudioDevice
==
device
)
140
return
;
141
142
m_resolvedAudioDevice
=
std
::
move
(
device
);
143
144
if
(!
m_player
)
145
return
;
146
147
for
(
const
auto
&
voice
:
m_voices
)
148
m_player
->
stop
(
voice
);
149
150
std
::
vector
<
std
::
shared_ptr
<
QSoundEffectVoice
>>
voices
{
151
std
::
make_move_iterator
(
m_voices
.
begin
()),
std
::
make_move_iterator
(
m_voices
.
end
())
152
};
153
m_voices
.
clear
();
154
155
bool
hasPlayer
=
updatePlayer
();
156
if
(!
hasPlayer
)
157
return
;
158
159
for
(
const
auto
&
voice
:
voices
)
160
// we re-allocate a new voice ID and play on the new player
161
play
(
voice
->
clone
());
162
}
163
164
void
QSoundEffectPrivateWithPlayer
::
resolveAudioDevice
()
165
{
166
if
(
m_audioDevice
.
isNull
())
167
m_defaultAudioDevice
=
QMediaDevices
::
defaultAudioOutput
();
168
setResolvedAudioDevice
(
m_audioDevice
.
isNull
() ?
m_defaultAudioDevice
:
m_audioDevice
);
169
}
170
171
QAudioDevice
QSoundEffectPrivateWithPlayer
::
audioDevice
()
const
172
{
173
return
m_audioDevice
;
174
}
175
176
bool
QSoundEffectPrivateWithPlayer
::
setSource
(
const
QUrl
&
url
,
QSampleCache
&
sampleCache
)
177
{
178
if
(
m_sampleLoadFuture
.
isValid
())
179
m_sampleLoadFuture
.
cancel
();
180
181
m_url
=
url
;
182
m_sample
= {};
183
184
if
(
url
.
isEmpty
()) {
185
setStatus
(
QSoundEffect
::
Null
);
186
return
false
;
187
}
188
189
if
(!
url
.
isValid
()) {
190
setStatus
(
QSoundEffect
::
Error
);
191
return
false
;
192
}
193
194
setStatus
(
QSoundEffect
::
Loading
);
195
196
m_sampleLoadFuture
=
197
sampleCache
.
requestSampleFuture
(
url
).
then
(
this
, [
this
](
SharedSamplePtr
result
) {
198
if
(
result
) {
199
if
(!
formatIsSupported
(
result
->
format
())) {
200
qWarning
(
"QSoundEffect: QSoundEffect only supports mono or stereo files"
);
201
setStatus
(
QSoundEffect
::
Error
);
202
return
;
203
}
204
205
m_sample
=
std
::
move
(
result
);
206
setStatus
(
QSoundEffect
::
Ready
);
207
bool
hasPlayer
=
updatePlayer
();
208
if
(
std
::
exchange
(
m_playPending
,
false
)) {
209
if
(
hasPlayer
)
210
play
();
211
}
212
}
else
{
213
qWarning
(
"QSoundEffect: Error decoding source %ls"
,
qUtf16Printable
(
m_url
.
toString
()));
214
setStatus
(
QSoundEffect
::
Error
);
215
}
216
});
217
218
return
true
;
219
}
220
221
QUrl
QSoundEffectPrivateWithPlayer
::
url
()
const
222
{
223
return
m_url
;
224
}
225
226
void
QSoundEffectPrivateWithPlayer
::
setStatus
(
QSoundEffect
::
Status
status
)
227
{
228
if
(
status
==
m_status
)
229
return
;
230
m_status
=
status
;
231
emit
q_ptr
->
statusChanged
();
232
}
233
234
QSoundEffect
::
Status
QSoundEffectPrivateWithPlayer
::
status
()
const
235
{
236
return
m_status
;
237
}
238
239
int
QSoundEffectPrivateWithPlayer
::
loopCount
()
const
240
{
241
return
m_loopCount
;
242
}
243
244
bool
QSoundEffectPrivateWithPlayer
::
setLoopCount
(
int
loopCount
)
245
{
246
if
(
loopCount
== 0)
247
loopCount
= 1;
248
249
if
(
loopCount
==
m_loopCount
)
250
return
false
;
251
252
m_loopCount
=
loopCount
;
253
254
if
(
m_voices
.
empty
())
255
return
true
;
256
257
const
std
::
shared_ptr
<
QSoundEffectVoice
> &
voice
= *
m_voices
.
rbegin
();
258
voice
->
m_loopsRemaining
.
store
(
loopCount
,
std
::
memory_order_relaxed
);
259
260
setLoopsRemaining
(
loopCount
);
261
262
return
true
;
263
}
264
265
int
QSoundEffectPrivateWithPlayer
::
loopsRemaining
()
const
266
{
267
if
(
m_voices
.
empty
())
268
return
0;
269
270
return
m_loopsRemaining
;
271
}
272
273
float
QSoundEffectPrivateWithPlayer
::
volume
()
const
274
{
275
return
m_volume
;
276
}
277
278
bool
QSoundEffectPrivateWithPlayer
::
setVolume
(
float
volume
)
279
{
280
if
(
m_volume
==
volume
)
281
return
false
;
282
283
m_volume
=
volume
;
284
for
(
const
auto
&
voice
:
m_voices
) {
285
m_player
->
visitVoiceRt
(
voice
, [
volume
](
QSoundEffectVoice
&
voice
) {
286
voice
.
m_volume
=
volume
;
287
});
288
}
289
return
true
;
290
}
291
292
bool
QSoundEffectPrivateWithPlayer
::
muted
()
const
293
{
294
return
m_muted
;
295
}
296
297
bool
QSoundEffectPrivateWithPlayer
::
setMuted
(
bool
muted
)
298
{
299
if
(
m_muted
==
muted
)
300
return
false
;
301
302
m_muted
=
muted
;
303
for
(
const
auto
&
voice
:
m_voices
) {
304
m_player
->
visitVoiceRt
(
voice
, [
muted
](
QSoundEffectVoice
&
voice
) {
305
voice
.
m_muted
=
muted
;
306
});
307
}
308
return
true
;
309
}
310
311
void
QSoundEffectPrivateWithPlayer
::
play
()
312
{
313
if
(!
m_sample
) {
314
m_playPending
=
true
;
315
return
;
316
}
317
318
// each `play` will start a new voice
319
Q_ASSERT
(
m_player
);
320
321
auto
voice
=
std
::
make_shared
<
QSoundEffectVoice
>(
QRtAudioEngine
::
allocateVoiceId
(),
m_sample
,
322
m_volume
,
m_muted
,
m_loopCount
);
323
324
play
(
std
::
move
(
voice
));
325
}
326
327
void
QSoundEffectPrivateWithPlayer
::
stop
()
328
{
329
size_t
activeVoices
=
m_voices
.
size
();
330
for
(
const
auto
&
voice
:
m_voices
)
331
m_player
->
stop
(
voice
->
voiceId
());
332
setLoopsRemaining
(0);
333
334
m_voices
.
clear
();
335
m_playPending
=
false
;
336
if
(
activeVoices
)
337
emit
q_ptr
->
playingChanged
();
338
}
339
340
bool
QSoundEffectPrivateWithPlayer
::
playing
()
const
341
{
342
return
!
m_voices
.
empty
();
343
}
344
345
void
QSoundEffectPrivateWithPlayer
::
play
(
std
::
shared_ptr
<
QSoundEffectVoice
>
voice
)
346
{
347
QObject
::
connect
(&
voice
->
m_currentLoopChanged
, &
QAutoResetEvent
::
activated
,
this
,
348
[
this
,
voiceId
=
voice
->
voiceId
()] {
349
auto
foundVoice
=
m_voices
.
find
(
voiceId
);
350
if
(
foundVoice
==
m_voices
.
end
())
351
return
;
352
353
if
(
voiceId
!=
activeVoice
())
354
return
;
355
356
setLoopsRemaining
((*
foundVoice
)->
loopsRemaining
());
357
});
358
359
m_player
->
play
(
voice
);
360
m_voices
.
insert
(
std
::
move
(
voice
));
361
setLoopsRemaining
(
m_loopCount
);
362
if
(
m_voices
.
size
() == 1)
363
emit
q_ptr
->
playingChanged
();
364
}
365
366
bool
QSoundEffectPrivateWithPlayer
::
updatePlayer
()
367
{
368
Q_ASSERT
(
m_voices
.
empty
());
369
QObject
::
disconnect
(
m_voiceFinishedConnection
);
370
371
m_player
= {};
372
if
(
m_resolvedAudioDevice
.
isNull
())
373
return
false
;
374
375
auto
player
=
QRtAudioEngine
::
getEngineFor
(
m_resolvedAudioDevice
,
m_sample
->
format
());
376
m_player
=
player
;
377
378
m_voiceFinishedConnection
=
QObject
::
connect
(
m_player
.
get
(), &
QRtAudioEngine
::
voiceFinished
,
379
this
, [
this
](
VoiceId
voiceId
) {
380
if
(
voiceId
==
activeVoice
())
381
setLoopsRemaining
(0);
382
383
auto
found
=
m_voices
.
find
(
voiceId
);
384
if
(
found
!=
m_voices
.
end
()) {
385
m_voices
.
erase
(
found
);
386
if
(
m_voices
.
empty
())
387
emit
q_ptr
->
playingChanged
();
388
}
389
});
390
return
true
;
391
}
392
393
std
::
optional
<
VoiceId
>
QSoundEffectPrivateWithPlayer
::
activeVoice
()
const
394
{
395
if
(
m_voices
.
empty
())
396
return
std
::
nullopt
;
397
return
(*
m_voices
.
rbegin
())->
voiceId
();
398
}
399
400
bool
QSoundEffectPrivateWithPlayer
::
formatIsSupported
(
const
QAudioFormat
&
fmt
)
401
{
402
switch
(
fmt
.
channelCount
()) {
403
case
1:
404
case
2:
405
return
true
;
406
default
:
407
return
false
;
408
}
409
}
410
411
void
QSoundEffectPrivateWithPlayer
::
setLoopsRemaining
(
int
loopsRemaining
)
412
{
413
if
(
loopsRemaining
==
m_loopsRemaining
)
414
return
;
415
m_loopsRemaining
=
loopsRemaining
;
416
emit
q_ptr
->
loopsRemainingChanged
();
417
}
418
419
}
// namespace QtMultimediaPrivate
420
421
QT_END_NAMESPACE
QtMultimediaPrivate::QSoundEffectVoice::m_volume
float m_volume
Definition
qsoundeffectwithplayer_p.h:49
QtMultimediaPrivate::QSoundEffectVoice::QSoundEffectVoice
QSoundEffectVoice(VoiceId voiceId, std::shared_ptr< const QSample > sample, float volume, bool muted, int totalLoopCount)
Definition
qsoundeffectwithplayer.cpp:29
QtMultimediaPrivate::QSoundEffectVoice::m_muted
bool m_muted
Definition
qsoundeffectwithplayer_p.h:50
QtMultimediaPrivate::QSoundEffectVoice::m_totalFrames
const int m_totalFrames
Definition
qsoundeffectwithplayer_p.h:45
QtMultimediaPrivate
Definition
qaudiosystem_p.h:41
qtmultimedia
src
multimedia
audio
qsoundeffectwithplayer.cpp
Generated on
for Qt by
1.14.0