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
/
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
QPlatformAudioSink
*
platformSink
=
QPlatformAudioSink
::
get
(
m_sink
);
87
88
if
(
role
)
89
platformSink
->
setRole
(*
role
);
90
if
(
nativePeriodFrames
)
91
platformSink
->
setNativePeriodFrames
(*
nativePeriodFrames
);
92
93
m_sink
.
start
([
this
](
QSpan
<
float
>
outputBuffer
) {
94
audioCallback
(
outputBuffer
);
95
});
96
97
// we start suspended
98
m_sink
.
suspend
();
99
100
QObject
::
connect
(&
m_sink
, &
QAudioSink
::
stateChanged
,
this
, [&](
QtAudio
::
State
state
) {
101
emit
stateChanged
(
sinkStateToEngineState
(
state
));
102
});
103
}
104
105
QRtAudioEngine
::~
QRtAudioEngine
()
106
{
107
m_sink
.
reset
();
108
109
// consume the ringbuffers
110
m_appToRt
.
consumeAll
([](
auto
) {
111
});
112
m_rtToApp
.
consumeAll
([](
auto
) {
113
});
114
}
115
116
void
QRtAudioEngine
::
play
(
SharedVoice
voice
)
117
{
118
auto
lock
=
std
::
lock_guard
{
m_mutex
};
119
120
// TODO: where do we expect reampling to happen?
121
Q_ASSERT
(
voice
->
format
() ==
m_sink
.
format
());
122
123
if
(
m_voices
.
empty
())
124
m_sink
.
resume
();
125
126
m_voices
.
insert
(
voice
);
127
128
sendAppToRtCommand
(
PlayCommand
{
129
std
::
move
(
voice
),
130
});
131
}
132
133
void
QRtAudioEngine
::
stop
(
const
SharedVoice
&
voice
)
134
{
135
stop
(
voice
->
voiceId
());
136
}
137
138
void
QRtAudioEngine
::
stop
(
VoiceId
voiceId
)
139
{
140
auto
lock
=
std
::
lock_guard
{
m_mutex
};
141
sendAppToRtCommand
(
StopCommand
{
voiceId
});
142
}
143
144
void
QRtAudioEngine
::
visitVoiceRt
(
VoiceId
voiceId
,
RtVoiceVisitor
fn
,
bool
visitorIsTrivial
)
145
{
146
auto
lock
=
std
::
lock_guard
{
m_mutex
};
147
148
if
(
visitorIsTrivial
) {
149
sendAppToRtCommand
(
VisitCommandTrivial
{
150
voiceId
,
151
std
::
move
(
fn
),
152
});
153
154
}
else
{
155
sendAppToRtCommand
(
VisitCommand
{
156
voiceId
,
157
std
::
move
(
fn
),
158
});
159
}
160
}
161
162
VoiceId
QRtAudioEngine
::
allocateVoiceId
()
163
{
164
static
std
::
atomic_uint64_t
allocator
{ 0 };
165
return
VoiceId
{
allocator
.
fetch_add
(1,
std
::
memory_order_relaxed
) };
166
}
167
168
QtAudio
::
State
QRtAudioEngine
::
audioState
()
const
169
{
170
return
sinkStateToEngineState
(
m_sink
.
state
());
171
}
172
173
void
QRtAudioEngine
::
audioCallback
(
QSpan
<
float
>
outputBuffer
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
174
{
175
runRtCommands
();
176
bool
sendNotification
=
sendPendingAppNotifications
();
177
178
std
::
fill
(
outputBuffer
.
begin
(),
outputBuffer
.
end
(), 0.f);
179
180
std
::
vector
<
SharedVoice
,
pmr
::
polymorphic_allocator
<
SharedVoice
>>
finishedVoices
{
181
m_rtMemoryPool
.
get
(),
182
};
183
184
for
(
const
SharedVoice
&
voice
:
m_rtVoiceRegistry
) {
185
Q_ASSERT
(
voice
.
use_count
() >= 2);
// voice in both m_rtVoiceRegistry and m_voices
186
187
VoicePlayResult
playResult
=
voice
->
play
(
outputBuffer
);
188
if
(
playResult
==
VoicePlayResult
::
Finished
)
189
finishedVoices
.
push_back
(
voice
);
190
}
191
192
if
(!
finishedVoices
.
empty
()) {
193
withRTSanDisabled
([&] {
194
for
(
const
SharedVoice
&
voice
:
finishedVoices
) {
195
m_rtVoiceRegistry
.
erase
(
voice
);
196
bool
stopSent
=
sendRtToAppNotification
(
StopNotification
{
voice
});
197
if
(
stopSent
)
198
sendNotification
=
true
;
199
}
200
});
201
}
202
203
// TODO: we should probably (soft)clip the output buffer
204
205
cleanupRetiredVoices
();
206
if
(
sendNotification
)
207
m_notificationEvent
.
set
();
208
}
209
210
void
QRtAudioEngine
::
cleanupRetiredVoices
()
noexcept
Q_DECL_NONBLOCKING_FUNCTION
211
{
212
bool
notifyApp
=
false
;
213
214
#
if
__cpp_lib_erase_if
>=
202002L
215
using
std
::
erase_if
;
216
#
else
217
auto
erase_if
= [](
auto
&
c
,
auto
&&
pred
) {
218
auto
old_size
=
c
.
size
();
219
for
(
auto
first
=
c
.
begin
(),
last
=
c
.
end
();
first
!=
last
;) {
220
if
(
pred
(*
first
))
221
first
=
c
.
erase
(
first
);
222
else
223
++
first
;
224
}
225
return
old_size
-
c
.
size
();
226
};
227
#
endif
228
withRTSanDisabled
([&] {
229
erase_if
(
m_rtVoiceRegistry
, [&](
const
SharedVoice
&
voice
) {
230
bool
voiceIsActive
=
voice
->
isActive
();
231
if
(!
voiceIsActive
)
232
notifyApp
=
sendRtToAppNotification
(
StopNotification
{
voice
});
233
234
return
!
voiceIsActive
;
235
});
236
});
237
238
if
(
notifyApp
)
239
m_notificationEvent
.
set
();
240
}
241
242
void
QRtAudioEngine
::
runRtCommands
()
noexcept
Q_DECL_NONBLOCKING_FUNCTION
243
{
244
m_appToRt
.
consumeAll
([&](
QSpan
<
RtCommand
>
commands
) {
245
for
(
RtCommand
&
cmd
:
commands
) {
246
std
::
visit
([&](
auto
cmd
) {
247
runRtCommand
(
std
::
move
(
cmd
));
248
},
std
::
move
(
cmd
));
249
}
250
});
251
}
252
253
void
QRtAudioEngine
::
runRtCommand
(
PlayCommand
cmd
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
254
{
255
withRTSanDisabled
([&] {
256
m_rtVoiceRegistry
.
insert
(
cmd
.
voice
);
257
});
258
}
259
260
void
QRtAudioEngine
::
runRtCommand
(
StopCommand
cmd
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
261
{
262
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
263
if
(
it
==
m_rtVoiceRegistry
.
end
())
264
return
;
265
266
SharedVoice
voice
= *
it
;
267
m_rtVoiceRegistry
.
erase
(
it
);
268
269
bool
emitNotify
=
sendRtToAppNotification
(
StopNotification
{
270
std
::
move
(
voice
),
271
});
272
if
(
emitNotify
)
273
m_notificationEvent
.
set
();
274
}
275
276
void
QRtAudioEngine
::
runRtCommand
(
VisitCommand
cmd
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
277
{
278
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
279
if
(
it
==
m_rtVoiceRegistry
.
end
())
280
return
;
281
282
cmd
.
callback
(**
it
);
283
284
// send callback back to application for destruction
285
bool
emitNotify
=
sendRtToAppNotification
(
VisitReply
{
286
std
::
move
(
cmd
.
callback
),
287
});
288
if
(
emitNotify
)
289
m_notificationEvent
.
set
();
290
}
291
292
void
QRtAudioEngine
::
runRtCommand
(
VisitCommandTrivial
cmd
)
noexcept
Q_DECL_NONBLOCKING_FUNCTION
293
{
294
auto
it
=
m_rtVoiceRegistry
.
find
(
cmd
.
voiceId
);
295
if
(
it
==
m_rtVoiceRegistry
.
end
())
296
return
;
297
298
cmd
.
callback
(**
it
);
299
}
300
301
void
QRtAudioEngine
::
runNonRtNotifications
()
302
{
303
std
::
vector
<
VoiceId
>
finishedVoices
;
304
{
305
auto
lock
=
std
::
lock_guard
{
m_mutex
};
306
m_rtToApp
.
consumeAll
([&](
QSpan
<
Notification
>
notifications
) {
307
for
(
Notification
&
notification
:
notifications
) {
308
std
::
visit
([&](
auto
notification
) {
309
runNonRtNotification
(
std
::
move
(
notification
));
310
},
std
::
move
(
notification
));
311
}
312
});
313
314
finishedVoices
=
std
::
move
(
m_finishedVoices
);
315
m_finishedVoices
.
clear
();
316
}
317
318
// emit voiceFinished outside of the lock
319
for
(
VoiceId
voiceId
:
finishedVoices
)
320
emit
voiceFinished
(
voiceId
);
321
}
322
323
void
QRtAudioEngine
::
runNonRtNotification
(
StopNotification
notification
)
324
{
325
m_voices
.
erase
(
notification
.
voice
);
326
if
(
m_voices
.
empty
())
327
m_sink
.
suspend
();
328
m_finishedVoices
.
push_back
(
notification
.
voice
->
voiceId
());
329
}
330
331
void
QRtAudioEngine
::
runNonRtNotification
(
VisitReply
)
332
{
333
// nop (just making sure to delete on the application thread);
334
}
335
336
void
QRtAudioEngine
::
sendAppToRtCommand
(
RtCommand
cmd
)
337
{
338
// first write all pending commands from overflow buffer
339
sendPendingRtCommands
();
340
341
bool
written
=
m_appToRt
.
produceOne
([&] {
342
return
std
::
move
(
cmd
);
343
});
344
345
if
(
written
)
346
return
;
347
348
m_appToRtOverflowBuffer
.
emplace_back
(
std
::
move
(
cmd
));
349
350
QMetaObject
::
invokeMethod
(&
m_pendingCommandsTimer
, [
this
] {
351
if
(!
m_pendingCommandsTimer
.
isActive
())
352
m_pendingCommandsTimer
.
start
();
353
});
354
}
355
356
bool
QRtAudioEngine
::
sendRtToAppNotification
(
Notification
cmd
)
357
{
358
// first write all pending commands from overflow buffer
359
bool
emitNotification
=
sendPendingAppNotifications
();
360
361
bool
written
=
m_rtToApp
.
produceOne
([&] {
362
return
std
::
move
(
cmd
);
363
});
364
365
if
(
written
)
366
return
true
;
367
368
m_rtToAppOverflowBuffer
.
emplace_back
(
std
::
move
(
cmd
));
369
370
return
emitNotification
;
371
}
372
373
void
QRtAudioEngine
::
sendPendingRtCommands
()
374
{
375
while
(!
m_appToRtOverflowBuffer
.
empty
()) {
376
Q_UNLIKELY_BRANCH
;
377
bool
written
=
m_appToRt
.
produceOne
([&] {
378
return
std
::
move
(
m_appToRtOverflowBuffer
.
front
());
379
});
380
if
(!
written
)
381
return
;
382
383
m_appToRtOverflowBuffer
.
pop_front
();
384
}
385
}
386
387
bool
QRtAudioEngine
::
sendPendingAppNotifications
()
388
{
389
bool
emitNotification
=
false
;
390
while
(!
m_rtToAppOverflowBuffer
.
empty
()) {
391
Q_UNLIKELY_BRANCH
;
392
393
// first write all pending commands from overflow buffer
394
bool
written
=
m_rtToApp
.
produceOne
([&] {
395
return
std
::
move
(
m_rtToAppOverflowBuffer
.
front
());
396
});
397
if
(!
written
)
398
break
;
399
400
m_rtToAppOverflowBuffer
.
pop_front
();
401
emitNotification
=
true
;
402
}
403
404
return
emitNotification
;
405
}
406
407
}
// namespace QtMultimediaPrivate
408
409
QT_END_NAMESPACE
410
411
#
ifdef
Q_CC_MINGW
412
QT_WARNING_POP
413
#
endif
414
415
#
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.16.1