Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Toggle main menu visibility
Loading...
Searching...
No Matches
qrtaudioengine.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
"qrtaudioengine_p.h"
5
6
#
include
<
QtMultimedia
/
private
/
qaudiosystem_p
.
h
>
7
#
include
<
QtMultimedia
/
private
/
qmemory_resource_tlsf_p
.
h
>
8
#
include
<
QtMultimedia
/
private
/
qtmm_rtsan_support_p
.
h
>
9
#
include
<
QtCore
/
q20map
.
h
>
10
#
include
<
QtCore
/
qcoreapplication
.
h
>
11
#
include
<
QtCore
/
qdebug
.
h
>
12
#
include
<
QtCore
/
qmutex
.
h
>
13
#
include
<
QtCore
/
qthread
.
h
>
14
15
#
include
<
mutex
>
16
17
#
ifdef
Q_CC_MINGW
18
// mingw-13.1 seems to have a false positive when using std::function inside a std::variant
19
QT_WARNING_PUSH
20
QT_WARNING_DISABLE_GCC(
"-Wmaybe-uninitialized"
)
21
#
endif
22
23
QT_BEGIN_NAMESPACE
24
25
namespace
QtMultimediaPrivate
{
26
27
using
namespace
QtPrivate
;
28
using
namespace
std
::chrono_literals;
29
30
static
QtAudio
::
State
sinkStateToEngineState
(QtAudio::State state)
31
{
32
switch
(state) {
33
case
QtAudio::ActiveState:
34
case
QtAudio::IdleState:
35
case
QtAudio::SuspendedState:
36
return
QtAudio::ActiveState;
37
case
QtAudio::StoppedState:
38
return
QtAudio::StoppedState;
39
default
:
40
Q_UNREACHABLE_RETURN(QtAudio::StoppedState);
41
}
42
}
43
44
///////////////////////////////////////////////////////////////////////////////////////////////////
45
46
QRtAudioEngine
::
QRtAudioEngine
(
const
QAudioDevice
&
device
,
const
QAudioFormat
&
format
,
47
std
::
optional
<
AudioEndpointRole
>
role
,
48
std
::
optional
<
NativePeriodFrames
>
nativePeriodFrames
)
49
:
QRtAudioEngine
{
MoveToApplicationThread
::
No
,
device
,
format
,
role
,
nativePeriodFrames
}
50
{
51
}
52
53
QRtAudioEngine
::
QRtAudioEngine
(
MoveToApplicationThread
moveToApplicationThread
,
54
const
QAudioDevice
&
device
,
const
QAudioFormat
&
format
,
55
std
::
optional
<
AudioEndpointRole
>
role
,
56
std
::
optional
<
NativePeriodFrames
>
nativePeriodFrames
)
57
:
m_sink
{
58
device
,
59
format
,
60
},
61
m_rtMemoryPool
{
62
std
::
make_unique
<
QTlsfMemoryResource
>(
poolSize
)
63
}
64
{
65
m_notificationEvent
.
callOnActivated
([
this
] {
66
runNonRtNotifications
();
67
});
68
69
if
(
moveToApplicationThread
==
MoveToApplicationThread
::
Yes
&& !
QThread
::
isMainThread
()) {
70
QThread
*
appThread
=
qApp
->
thread
();
71
moveToThread
(
appThread
);
72
m_sink
.
moveToThread
(
appThread
);
73
m_notificationEvent
.
moveToThread
(
appThread
);
74
m_pendingCommandsTimer
.
moveToThread
(
appThread
);
75
}
76
77
m_pendingCommandsTimer
.
setInterval
(10ms);
78
m_pendingCommandsTimer
.
setTimerType
(
Qt
::
CoarseTimer
);
79
m_pendingCommandsTimer
.
callOnTimeout
(&
m_pendingCommandsTimer
, [
this
] {
80
auto
lock
=
std
::
lock_guard
{
m_mutex
};
81
sendPendingRtCommands
();
82
if
(
m_appToRtOverflowBuffer
.
empty
())
83
m_pendingCommandsTimer
.
stop
();
84
});
85
86
QMetaObject
::
invokeMethod
(
this
, [
this
,
role
,
nativePeriodFrames
] {
87
QPlatformAudioSink
*
platformSink
=
QPlatformAudioSink
::
get
(
m_sink
);
88
89
if
(
role
)
90
platformSink
->
setRole
(*
role
);
91
if
(
nativePeriodFrames
)
92
platformSink
->
setNativePeriodFrames
(*
nativePeriodFrames
);
93
94
m_sink
.
start
([
this
](
QSpan
<
float
>
outputBuffer
) {
95
audioCallback
(
outputBuffer
);
96
});
97
98
// we start suspended
99
m_sink
.
suspend
();
100
101
QObject
::
connect
(&
m_sink
, &
QAudioSink
::
stateChanged
,
this
, [&](
QtAudio
::
State
state
) {
102
Q_EMIT
stateChanged
(
sinkStateToEngineState
(
state
));
103
});
104
});
105
}
106
107
QRtAudioEngine
::~
QRtAudioEngine
()
108
{
109
m_sink
.
reset
();
110
111
// consume the ringbuffers
112
m_appToRt
.
consumeAll
([](
auto
) {
113
});
114
m_rtToApp
.
consumeAll
([](
auto
) {
115
});
116
}
117
118
void
QRtAudioEngine
::
play
(
SharedVoice
voice
)
119
{
120
auto
lock
=
std
::
lock_guard
{
m_mutex
};
121
122
// TODO: where do we expect reampling to happen?
123
Q_ASSERT
(
voice
->
format
() ==
m_sink
.
format
());
124
125
if
(
m_voices
.
empty
()) {
126
QMetaObject
::
invokeMethod
(
this
, [
this
] {
127
m_sink
.
resume
();
128
});
129
}
130
131
m_voices
.
insert
(
voice
);
132
133
sendAppToRtCommand
(
PlayCommand
{
134
std
::
move
(
voice
),
135
});
136
}
137
138
void
QRtAudioEngine
::
stop
(
const
SharedVoice
&
voice
)
139
{
140
stop
(
voice
->
voiceId
());
141
}
142
143
void
QRtAudioEngine
::
release
(
SharedVoice
voice
)
144
{
145
if
(
thread
()->
isCurrentThread
())
146
return
;
147
148
QMetaObject
::
invokeMethod
(
this
, [
voice
=
std
::
move
(
voice
)] {
149
// release the voice on the owning thread
150
});
151
}
152
153
void
QRtAudioEngine
::
stop
(
VoiceId
voiceId
)
154
{
155
auto
lock
=
std
::
lock_guard
{
m_mutex
};
156
sendAppToRtCommand
(
StopCommand
{
voiceId
});
157
}
158
159
void
QRtAudioEngine
::
visitVoiceRt
(
VoiceId
voiceId
,
RtVoiceVisitor
fn
,
bool
visitorIsTrivial
)
160
{
161
auto
lock
=
std
::
lock_guard
{
m_mutex
};
162
163
if
(
visitorIsTrivial
) {
164
sendAppToRtCommand
(
VisitCommandTrivial
{
165
voiceId
,
166
std
::
move
(
fn
),
167
});
168
169
}
else
{
170
sendAppToRtCommand
(
VisitCommand
{
171
voiceId
,
172
std
::
move
(
fn
),
173
});
174
}
175
}
176
177
VoiceId
QRtAudioEngine
::
allocateVoiceId
()
178
{
179
static
std
::
atomic_uint64_t
allocator
{ 0 };
180
return
VoiceId
{
allocator
.
fetch_add
(1,
std
::
memory_order_relaxed
) };
181
}
182
183
QtAudio
::
State
QRtAudioEngine
::
audioState
()
const
184
{
185
return
sinkStateToEngineState
(
m_sink
.
state
());
186
}
187
188
void
QRtAudioEngine
::
audioCallback
(
QSpan
<
float
>
outputBuffer
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
189
{
190
runRtCommands
();
191
bool
sendNotification
=
sendPendingAppNotifications
();
192
193
std
::
fill
(
outputBuffer
.
begin
(),
outputBuffer
.
end
(), 0.f);
194
195
std
::
vector
<
SharedVoice
,
pmr
::
polymorphic_allocator
<
SharedVoice
>>
finishedVoices
{
196
m_rtMemoryPool
.
get
(),
197
};
198
199
for
(
const
SharedVoice
&
voice
:
m_rtVoiceRegistry
) {
200
Q_ASSERT
(
voice
.
use_count
() >= 2);
// voice in both m_rtVoiceRegistry and m_voices
201
202
VoicePlayResult
playResult
=
voice
->
play
(
outputBuffer
);
203
if
(
playResult
==
VoicePlayResult
::
Finished
)
204
finishedVoices
.
push_back
(
voice
);
205
}
206
207
if
(!
finishedVoices
.
empty
()) {
208
withRTSanDisabled
([&] {
209
for
(
const
SharedVoice
&
voice
:
finishedVoices
) {
210
m_rtVoiceRegistry
.
erase
(
voice
);
211
bool
stopSent
=
sendRtToAppNotification
(
StopNotification
{
voice
});
212
if
(
stopSent
)
213
sendNotification
=
true
;
214
}
215
});
216
}
217
218
// TODO: we should probably (soft)clip the output buffer
219
220
cleanupRetiredVoices
();
221
if
(
sendNotification
)
222
m_notificationEvent
.
set
();
223
}
224
225
void
QRtAudioEngine
::
cleanupRetiredVoices
()
noexcept
Q_DECL_NONBLOCKING_FUNCTION
226
{
227
bool
notifyApp
=
false
;
228
229
#
if
__cpp_lib_erase_if
>=
202002L
230
using
std
::
erase_if
;
231
#
else
232
auto
erase_if
= [](
auto
&
c
,
auto
&&
pred
) {
233
auto
old_size
=
c
.
size
();
234
for
(
auto
first
=
c
.
begin
(),
last
=
c
.
end
();
first
!=
last
;) {
235
if
(
pred
(*
first
))
236
first
=
c
.
erase
(
first
);
237
else
238
++
first
;
239
}
240
return
old_size
-
c
.
size
();
241
};
242
#
endif
243
withRTSanDisabled
([&] {
244
erase_if
(
m_rtVoiceRegistry
, [&](
const
SharedVoice
&
voice
) {
245
bool
voiceIsActive
=
voice
->
isActive
();
246
if
(!
voiceIsActive
)
247
notifyApp
=
sendRtToAppNotification
(
StopNotification
{
voice
});
248
249
return
!
voiceIsActive
;
250
});
251
});
252
253
if
(
notifyApp
)
254
m_notificationEvent
.
set
();
255
}
256
257
void
QRtAudioEngine
::
runRtCommands
()
noexcept
Q_DECL_NONBLOCKING_FUNCTION
258
{
259
m_appToRt
.
consumeAll
([&](
QSpan
<
RtCommand
>
commands
) {
260
for
(
RtCommand
&
cmd
:
commands
) {
261
std
::
visit
([&](
auto
cmd
) {
262
runRtCommand
(
std
::
move
(
cmd
));
263
},
std
::
move
(
cmd
));
264
}
265
});
266
}
267
268
void
QRtAudioEngine
::
runRtCommand
(
PlayCommand
cmd
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
269
{
270
withRTSanDisabled
([&] {
271
m_rtVoiceRegistry
.
insert
(
cmd
.
voice
);
272
});
273
}
274
275
void
QRtAudioEngine
::
runRtCommand
(
StopCommand
cmd
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
276
{
277
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
278
if
(
it
==
m_rtVoiceRegistry
.
end
())
279
return
;
280
281
SharedVoice
voice
= *
it
;
282
m_rtVoiceRegistry
.
erase
(
it
);
283
284
bool
emitNotify
=
sendRtToAppNotification
(
StopNotification
{
285
std
::
move
(
voice
),
286
});
287
if
(
emitNotify
)
288
m_notificationEvent
.
set
();
289
}
290
291
void
QRtAudioEngine
::
runRtCommand
(
VisitCommand
cmd
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
292
{
293
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
294
if
(
it
==
m_rtVoiceRegistry
.
end
())
295
return
;
296
297
cmd
.
callback
(**
it
);
298
299
// send callback back to application for destruction
300
bool
emitNotify
=
sendRtToAppNotification
(
VisitReply
{
301
std
::
move
(
cmd
.
callback
),
302
});
303
if
(
emitNotify
)
304
m_notificationEvent
.
set
();
305
}
306
307
void
QRtAudioEngine
::
runRtCommand
(
VisitCommandTrivial
cmd
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
308
{
309
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
310
if
(
it
==
m_rtVoiceRegistry
.
end
())
311
return
;
312
313
cmd
.
callback
(**
it
);
314
}
315
316
void
QRtAudioEngine
::
runNonRtNotifications
()
317
{
318
std
::
vector
<
VoiceId
>
finishedVoices
;
319
{
320
auto
lock
=
std
::
lock_guard
{
m_mutex
};
321
m_rtToApp
.
consumeAll
([&](
QSpan
<
Notification
>
notifications
) {
322
for
(
Notification
&
notification
:
notifications
) {
323
std
::
visit
([&](
auto
notification
) {
324
runNonRtNotification
(
std
::
move
(
notification
));
325
},
std
::
move
(
notification
));
326
}
327
});
328
329
finishedVoices
=
std
::
move
(
m_finishedVoices
);
330
m_finishedVoices
.
clear
();
331
}
332
333
// emit voiceFinished outside of the lock
334
for
(
VoiceId
voiceId
:
finishedVoices
)
335
Q_EMIT
voiceFinished
(
voiceId
);
336
}
337
338
void
QRtAudioEngine
::
runNonRtNotification
(
StopNotification
notification
)
339
{
340
m_voices
.
erase
(
notification
.
voice
);
341
if
(
m_voices
.
empty
())
342
m_sink
.
suspend
();
343
m_finishedVoices
.
push_back
(
notification
.
voice
->
voiceId
());
344
}
345
346
void
QRtAudioEngine
::
runNonRtNotification
(
VisitReply
)
347
{
348
// nop (just making sure to delete on the application thread);
349
}
350
351
void
QRtAudioEngine
::
sendAppToRtCommand
(
RtCommand
cmd
)
352
{
353
// first write all pending commands from overflow buffer
354
sendPendingRtCommands
();
355
356
bool
written
=
m_appToRt
.
produceOne
([&] {
357
return
std
::
move
(
cmd
);
358
});
359
360
if
(
written
)
361
return
;
362
363
m_appToRtOverflowBuffer
.
emplace_back
(
std
::
move
(
cmd
));
364
365
QMetaObject
::
invokeMethod
(&
m_pendingCommandsTimer
, [
this
] {
366
if
(!
m_pendingCommandsTimer
.
isActive
())
367
m_pendingCommandsTimer
.
start
();
368
});
369
}
370
371
bool
QRtAudioEngine
::
sendRtToAppNotification
(
Notification
cmd
)
372
{
373
// first write all pending commands from overflow buffer
374
bool
emitNotification
=
sendPendingAppNotifications
();
375
376
bool
written
=
m_rtToApp
.
produceOne
([&] {
377
return
std
::
move
(
cmd
);
378
});
379
380
if
(
written
)
381
return
true
;
382
383
m_rtToAppOverflowBuffer
.
emplace_back
(
std
::
move
(
cmd
));
384
385
return
emitNotification
;
386
}
387
388
void
QRtAudioEngine
::
sendPendingRtCommands
()
389
{
390
while
(!
m_appToRtOverflowBuffer
.
empty
()) {
391
Q_UNLIKELY_BRANCH
;
392
bool
written
=
m_appToRt
.
produceOne
([&] {
393
return
std
::
move
(
m_appToRtOverflowBuffer
.
front
());
394
});
395
if
(!
written
)
396
return
;
397
398
m_appToRtOverflowBuffer
.
pop_front
();
399
}
400
}
401
402
bool
QRtAudioEngine
::
sendPendingAppNotifications
()
403
{
404
bool
emitNotification
=
false
;
405
while
(!
m_rtToAppOverflowBuffer
.
empty
()) {
406
Q_UNLIKELY_BRANCH
;
407
408
// first write all pending commands from overflow buffer
409
bool
written
=
m_rtToApp
.
produceOne
([&] {
410
return
std
::
move
(
m_rtToAppOverflowBuffer
.
front
());
411
});
412
if
(!
written
)
413
break
;
414
415
m_rtToAppOverflowBuffer
.
pop_front
();
416
emitNotification
=
true
;
417
}
418
419
return
emitNotification
;
420
}
421
422
}
// namespace QtMultimediaPrivate
423
424
QT_END_NAMESPACE
425
426
#
ifdef
Q_CC_MINGW
427
QT_WARNING_POP
428
#
endif
429
430
#
include
"moc_qrtaudioengine_p.cpp"
QtMultimediaPrivate::impl
Definition
qaudiosystem_p.h:257
QtMultimediaPrivate::sinkStateToEngineState
static QtAudio::State sinkStateToEngineState(QtAudio::State state)
Definition
qrtaudioengine.cpp:30
std
[33]
Definition
src_corelib_tools_qhash.cpp:421
qtmultimedia
src
multimedia
audio
qrtaudioengine.cpp
Generated on
for Qt by
1.18.0