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
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
19QT_WARNING_PUSH
20QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
21#endif
22
23QT_BEGIN_NAMESPACE
24
25namespace QtMultimediaPrivate {
26
27using namespace QtPrivate;
28using namespace std::chrono_literals;
29
30static 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
52
57 : m_sink{
58 device,
59 format,
60 },
63 }
64{
67 });
68
75 }
76
80 auto lock = std::lock_guard{ m_mutex };
84 });
85
88
89 if (role)
93
94 m_sink.start([this](QSpan<float> outputBuffer) {
96 });
97
98 // we start suspended
100
103 });
104 });
105}
106
108{
109 m_sink.reset();
110
111 // consume the ringbuffers
112 m_appToRt.consumeAll([](auto) {
113 });
114 m_rtToApp.consumeAll([](auto) {
115 });
116}
117
119{
120 auto lock = std::lock_guard{ m_mutex };
121
122 // TODO: where do we expect reampling to happen?
124
125 if (m_voices.empty()) {
126 QMetaObject::invokeMethod(this, [this] {
127 m_sink.resume();
128 });
129 }
130
132
134 std::move(voice),
135 });
136}
137
139{
140 stop(voice->voiceId());
141}
142
144{
145 if (thread()->isCurrentThread())
146 return;
147
149 // release the voice on the owning thread
150 });
151}
152
158
160{
161 auto lock = std::lock_guard{ m_mutex };
162
163 if (visitorIsTrivial) {
165 voiceId,
166 std::move(fn),
167 });
168
169 } else {
171 voiceId,
172 std::move(fn),
173 });
174 }
175}
176
182
187
189{
192
194
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
205 }
206
207 if (!finishedVoices.empty()) {
209 for (const SharedVoice &voice : finishedVoices) {
212 if (stopSent)
213 sendNotification = true;
214 }
215 });
216 }
217
218 // TODO: we should probably (soft)clip the output buffer
219
223}
224
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
245 bool voiceIsActive = voice->isActive();
246 if (!voiceIsActive)
248
249 return !voiceIsActive;
250 });
251 });
252
253 if (notifyApp)
255}
256
258{
260 for (RtCommand &cmd : commands) {
261 std::visit([&](auto cmd) {
263 }, std::move(cmd));
264 }
265 });
266}
267
269{
272 });
273}
274
276{
278 if (it == m_rtVoiceRegistry.end())
279 return;
280
283
285 std::move(voice),
286 });
287 if (emitNotify)
289}
290
292{
294 if (it == m_rtVoiceRegistry.end())
295 return;
296
297 cmd.callback(**it);
298
299 // send callback back to application for destruction
302 });
303 if (emitNotify)
305}
306
308{
310 if (it == m_rtVoiceRegistry.end())
311 return;
312
313 cmd.callback(**it);
314}
315
317{
319 {
320 auto lock = std::lock_guard{ m_mutex };
323 std::visit([&](auto notification) {
325 }, std::move(notification));
326 }
327 });
328
331 }
332
333 // emit voiceFinished outside of the lock
336}
337
339{
341 if (m_voices.empty())
342 m_sink.suspend();
344}
345
347{
348 // nop (just making sure to delete on the application thread);
349}
350
352{
353 // first write all pending commands from overflow buffer
355
356 bool written = m_appToRt.produceOne([&] {
357 return std::move(cmd);
358 });
359
360 if (written)
361 return;
362
364
368 });
369}
370
372{
373 // first write all pending commands from overflow buffer
375
376 bool written = m_rtToApp.produceOne([&] {
377 return std::move(cmd);
378 });
379
380 if (written)
381 return true;
382
384
385 return emitNotification;
386}
387
389{
390 while (!m_appToRtOverflowBuffer.empty()) {
392 bool written = m_appToRt.produceOne([&] {
394 });
395 if (!written)
396 return;
397
399 }
400}
401
403{
404 bool emitNotification = false;
405 while (!m_rtToAppOverflowBuffer.empty()) {
407
408 // first write all pending commands from overflow buffer
409 bool written = m_rtToApp.produceOne([&] {
411 });
412 if (!written)
413 break;
414
416 emitNotification = true;
417 }
418
419 return emitNotification;
420}
421
422} // namespace QtMultimediaPrivate
423
424QT_END_NAMESPACE
425
426#ifdef Q_CC_MINGW
427QT_WARNING_POP
428#endif
429
430#include "moc_qrtaudioengine_p.cpp"
static QtAudio::State sinkStateToEngineState(QtAudio::State state)