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