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
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
/
qaudio_rtsan_support_p
.
h
>
7
#
include
<
QtMultimedia
/
private
/
qaudiosystem_p
.
h
>
8
#
include
<
QtMultimedia
/
private
/
qmemory_resource_tlsf_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
///////////////////////////////////////////////////////////////////////////////////////////////////
31
32
QRtAudioEngine
::
QRtAudioEngine
(
const
QAudioDevice
&
device
,
const
QAudioFormat
&
format
,
33
std
::
optional
<
AudioEndpointRole
>
role
,
34
std
::
optional
<
uint32_t
>
hardwareBufferFrames
)
35
:
m_sink
{
36
device
,
37
format
,
38
},
39
m_rtMemoryPool
{
40
std
::
make_unique
<
QTlsfMemoryResource
>(
poolSize
)
41
}
42
{
43
m_notificationEvent
.
callOnActivated
([
this
] {
44
runNonRtNotifications
();
45
});
46
47
if
(!
QThread
::
isMainThread
()) {
48
QThread
*
appThread
=
qApp
->
thread
();
49
moveToThread
(
appThread
);
50
m_sink
.
moveToThread
(
appThread
);
51
m_notificationEvent
.
moveToThread
(
appThread
);
52
m_pendingCommandsTimer
.
moveToThread
(
appThread
);
53
}
54
55
m_pendingCommandsTimer
.
setInterval
(10ms);
56
m_pendingCommandsTimer
.
setTimerType
(
Qt
::
CoarseTimer
);
57
m_pendingCommandsTimer
.
callOnTimeout
(&
m_pendingCommandsTimer
, [
this
] {
58
auto
lock
=
std
::
lock_guard
{
m_mutex
};
59
sendPendingRtCommands
();
60
if
(
m_appToRtOverflowBuffer
.
empty
())
61
m_pendingCommandsTimer
.
stop
();
62
});
63
64
QPlatformAudioSink
*
platformSink
=
QPlatformAudioSink
::
get
(
m_sink
);
65
66
if
(
role
)
67
platformSink
->
setRole
(*
role
);
68
if
(
hardwareBufferFrames
)
69
platformSink
->
setHardwareBufferFrames
(*
hardwareBufferFrames
);
70
71
m_sink
.
start
([
this
](
QSpan
<
float
>
outputBuffer
) {
72
audioCallback
(
outputBuffer
);
73
});
74
75
// we start suspended
76
m_sink
.
suspend
();
77
}
78
79
QRtAudioEngine
::~
QRtAudioEngine
()
80
{
81
m_sink
.
reset
();
82
83
// consume the ringbuffers
84
m_appToRt
.
consumeAll
([](
auto
) {
85
});
86
m_rtToApp
.
consumeAll
([](
auto
) {
87
});
88
}
89
90
void
QRtAudioEngine
::
play
(
SharedVoice
voice
)
91
{
92
auto
lock
=
std
::
lock_guard
{
m_mutex
};
93
94
// TODO: where do we expect reampling to happen?
95
Q_ASSERT
(
voice
->
format
() ==
m_sink
.
format
());
96
97
if
(
m_voices
.
empty
())
98
m_sink
.
resume
();
99
100
m_voices
.
insert
(
voice
);
101
102
sendAppToRtCommand
(
PlayCommand
{
103
std
::
move
(
voice
),
104
});
105
}
106
107
void
QRtAudioEngine
::
stop
(
const
SharedVoice
&
voice
)
108
{
109
stop
(
voice
->
voiceId
());
110
}
111
112
void
QRtAudioEngine
::
stop
(
VoiceId
voiceId
)
113
{
114
auto
lock
=
std
::
lock_guard
{
m_mutex
};
115
sendAppToRtCommand
(
StopCommand
{
voiceId
});
116
}
117
118
void
QRtAudioEngine
::
visitVoiceRt
(
VoiceId
voiceId
,
RtVoiceVisitor
fn
,
bool
visitorIsTrivial
)
119
{
120
auto
lock
=
std
::
lock_guard
{
m_mutex
};
121
122
if
(
visitorIsTrivial
) {
123
sendAppToRtCommand
(
VisitCommandTrivial
{
124
voiceId
,
125
std
::
move
(
fn
),
126
});
127
128
}
else
{
129
sendAppToRtCommand
(
VisitCommand
{
130
voiceId
,
131
std
::
move
(
fn
),
132
});
133
}
134
}
135
136
VoiceId
QRtAudioEngine
::
allocateVoiceId
()
137
{
138
static
std
::
atomic_uint64_t
allocator
{ 0 };
139
return
VoiceId
{
allocator
.
fetch_add
(1,
std
::
memory_order_relaxed
) };
140
}
141
142
void
QRtAudioEngine
::
audioCallback
(
QSpan
<
float
>
outputBuffer
)
noexcept
QT_MM_NONBLOCKING
143
{
144
runRtCommands
();
145
bool
sendNotification
=
sendPendingAppNotifications
();
146
147
std
::
fill
(
outputBuffer
.
begin
(),
outputBuffer
.
end
(), 0.f);
148
149
std
::
vector
<
SharedVoice
,
pmr
::
polymorphic_allocator
<
SharedVoice
>>
finishedVoices
{
150
m_rtMemoryPool
.
get
(),
151
};
152
153
for
(
const
SharedVoice
&
voice
:
m_rtVoiceRegistry
) {
154
Q_ASSERT
(
voice
.
use_count
() >= 2);
// voice in both m_rtVoiceRegistry and m_voices
155
156
VoicePlayResult
playResult
=
voice
->
play
(
outputBuffer
);
157
if
(
playResult
==
VoicePlayResult
::
Finished
)
158
finishedVoices
.
push_back
(
voice
);
159
}
160
161
if
(!
finishedVoices
.
empty
()) {
162
withRTSanDisabled
([&] {
163
for
(
const
SharedVoice
&
voice
:
finishedVoices
) {
164
m_rtVoiceRegistry
.
erase
(
voice
);
165
bool
stopSent
=
sendRtToAppNotification
(
StopNotification
{
voice
});
166
if
(
stopSent
)
167
sendNotification
=
true
;
168
}
169
});
170
}
171
172
// TODO: we should probably (soft)clip the output buffer
173
174
cleanupRetiredVoices
();
175
if
(
sendNotification
)
176
m_notificationEvent
.
set
();
177
}
178
179
void
QRtAudioEngine
::
cleanupRetiredVoices
()
noexcept
QT_MM_NONBLOCKING
180
{
181
bool
notifyApp
=
false
;
182
183
#
if
__cpp_lib_erase_if
>=
202002L
184
using
std
::
erase_if
;
185
#
else
186
auto
erase_if
= [](
auto
&
c
,
auto
&&
pred
) {
187
auto
old_size
=
c
.
size
();
188
for
(
auto
first
=
c
.
begin
(),
last
=
c
.
end
();
first
!=
last
;) {
189
if
(
pred
(*
first
))
190
first
=
c
.
erase
(
first
);
191
else
192
++
first
;
193
}
194
return
old_size
-
c
.
size
();
195
};
196
#
endif
197
withRTSanDisabled
([&] {
198
erase_if
(
m_rtVoiceRegistry
, [&](
const
SharedVoice
&
voice
) {
199
bool
voiceIsActive
=
voice
->
isActive
();
200
if
(!
voiceIsActive
)
201
notifyApp
=
sendRtToAppNotification
(
StopNotification
{
voice
});
202
203
return
!
voiceIsActive
;
204
});
205
});
206
207
if
(
notifyApp
)
208
m_notificationEvent
.
set
();
209
}
210
211
void
QRtAudioEngine
::
runRtCommands
()
noexcept
QT_MM_NONBLOCKING
212
{
213
m_appToRt
.
consumeAll
([&](
QSpan
<
RtCommand
>
commands
) {
214
for
(
RtCommand
&
cmd
:
commands
) {
215
std
::
visit
([&](
auto
cmd
) {
216
runRtCommand
(
std
::
move
(
cmd
));
217
},
std
::
move
(
cmd
));
218
}
219
});
220
}
221
222
void
QRtAudioEngine
::
runRtCommand
(
PlayCommand
cmd
)
noexcept
QT_MM_NONBLOCKING
223
{
224
withRTSanDisabled
([&] {
225
m_rtVoiceRegistry
.
insert
(
cmd
.
voice
);
226
});
227
}
228
229
void
QRtAudioEngine
::
runRtCommand
(
StopCommand
cmd
)
noexcept
QT_MM_NONBLOCKING
230
{
231
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
232
if
(
it
==
m_rtVoiceRegistry
.
end
())
233
return
;
234
235
SharedVoice
voice
= *
it
;
236
m_rtVoiceRegistry
.
erase
(
it
);
237
238
bool
emitNotify
=
sendRtToAppNotification
(
StopNotification
{
239
std
::
move
(
voice
),
240
});
241
if
(
emitNotify
)
242
m_notificationEvent
.
set
();
243
}
244
245
void
QRtAudioEngine
::
runRtCommand
(
VisitCommand
cmd
)
noexcept
QT_MM_NONBLOCKING
246
{
247
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
248
if
(
it
==
m_rtVoiceRegistry
.
end
())
249
return
;
250
251
cmd
.
callback
(**
it
);
252
253
// send callback back to application for destruction
254
bool
emitNotify
=
sendRtToAppNotification
(
VisitReply
{
255
std
::
move
(
cmd
.
callback
),
256
});
257
if
(
emitNotify
)
258
m_notificationEvent
.
set
();
259
}
260
261
void
QRtAudioEngine
::
runRtCommand
(
VisitCommandTrivial
cmd
)
noexcept
QT_MM_NONBLOCKING
262
{
263
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
264
if
(
it
==
m_rtVoiceRegistry
.
end
())
265
return
;
266
267
cmd
.
callback
(**
it
);
268
}
269
270
void
QRtAudioEngine
::
runNonRtNotifications
()
271
{
272
std
::
vector
<
VoiceId
>
finishedVoices
;
273
{
274
auto
lock
=
std
::
lock_guard
{
m_mutex
};
275
m_rtToApp
.
consumeAll
([&](
QSpan
<
Notification
>
notifications
) {
276
for
(
Notification
&
notification
:
notifications
) {
277
std
::
visit
([&](
auto
notification
) {
278
runNonRtNotification
(
std
::
move
(
notification
));
279
},
std
::
move
(
notification
));
280
}
281
});
282
283
finishedVoices
=
std
::
move
(
m_finishedVoices
);
284
m_finishedVoices
.
clear
();
285
}
286
287
// emit voiceFinished outside of the lock
288
for
(
VoiceId
voiceId
:
finishedVoices
)
289
emit
voiceFinished
(
voiceId
);
290
}
291
292
void
QRtAudioEngine
::
runNonRtNotification
(
StopNotification
notification
)
293
{
294
m_voices
.
erase
(
notification
.
voice
);
295
if
(
m_voices
.
empty
())
296
m_sink
.
suspend
();
297
m_finishedVoices
.
push_back
(
notification
.
voice
->
voiceId
());
298
}
299
300
void
QRtAudioEngine
::
runNonRtNotification
(
VisitReply
)
301
{
302
// nop (just making sure to delete on the application thread);
303
}
304
305
void
QRtAudioEngine
::
sendAppToRtCommand
(
RtCommand
cmd
)
306
{
307
// first write all pending commands from overflow buffer
308
sendPendingRtCommands
();
309
310
bool
written
=
m_appToRt
.
produceOne
([&] {
311
return
std
::
move
(
cmd
);
312
});
313
314
if
(
written
)
315
return
;
316
317
m_appToRtOverflowBuffer
.
emplace_back
(
std
::
move
(
cmd
));
318
319
QMetaObject
::
invokeMethod
(&
m_pendingCommandsTimer
, [
this
] {
320
if
(!
m_pendingCommandsTimer
.
isActive
())
321
m_pendingCommandsTimer
.
start
();
322
});
323
}
324
325
bool
QRtAudioEngine
::
sendRtToAppNotification
(
Notification
cmd
)
326
{
327
// first write all pending commands from overflow buffer
328
bool
emitNotification
=
sendPendingAppNotifications
();
329
330
bool
written
=
m_rtToApp
.
produceOne
([&] {
331
return
std
::
move
(
cmd
);
332
});
333
334
if
(
written
)
335
return
true
;
336
337
m_rtToAppOverflowBuffer
.
emplace_back
(
std
::
move
(
cmd
));
338
339
return
emitNotification
;
340
}
341
342
void
QRtAudioEngine
::
sendPendingRtCommands
()
343
{
344
while
(!
m_appToRtOverflowBuffer
.
empty
()) {
345
Q_UNLIKELY_BRANCH
;
346
bool
written
=
m_appToRt
.
produceOne
([&] {
347
return
std
::
move
(
m_appToRtOverflowBuffer
.
front
());
348
});
349
if
(!
written
)
350
return
;
351
352
m_appToRtOverflowBuffer
.
pop_front
();
353
}
354
}
355
356
bool
QRtAudioEngine
::
sendPendingAppNotifications
()
357
{
358
bool
emitNotification
=
false
;
359
while
(!
m_rtToAppOverflowBuffer
.
empty
()) {
360
Q_UNLIKELY_BRANCH
;
361
362
// first write all pending commands from overflow buffer
363
bool
written
=
m_rtToApp
.
produceOne
([&] {
364
return
std
::
move
(
m_rtToAppOverflowBuffer
.
front
());
365
});
366
if
(!
written
)
367
break
;
368
369
m_rtToAppOverflowBuffer
.
pop_front
();
370
emitNotification
=
true
;
371
}
372
373
return
emitNotification
;
374
}
375
376
}
// namespace QtMultimediaPrivate
377
378
QT_END_NAMESPACE
379
380
#
ifdef
Q_CC_MINGW
381
QT_WARNING_POP
382
#
endif
383
384
#
include
"moc_qrtaudioengine_p.cpp"
QtMultimediaPrivate
Definition
qaudiosystem_p.h:49
std
[33]
Definition
src_corelib_tools_qhash.cpp:421
qtmultimedia
src
multimedia
audio
qrtaudioengine.cpp
Generated on
for Qt by
1.16.1