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 <QtCore/qcoreapplication.h>
7#include <QtCore/qdebug.h>
8#include <QtCore/qmutex.h>
9#include <QtCore/qthread.h>
10
11#include <QtMultimedia/private/qaudio_rtsan_support_p.h>
12#include <QtMultimedia/private/qaudiosystem_p.h>
13#include <QtMultimedia/private/qmemory_resource_tlsf_p.h>
14
15#include <QtCore/q20map.h>
16#include <mutex>
17
18#ifdef Q_CC_MINGW
19// mingw-13.1 seems to have a false positive when using std::function inside a std::variant
20QT_WARNING_PUSH
21QT_WARNING_DISABLE_GCC("-Wmaybe-uninitialized")
22#endif
23
24QT_BEGIN_NAMESPACE
25
26namespace QtMultimediaPrivate {
27
28using namespace QtPrivate;
29using namespace std::chrono_literals;
30
31///////////////////////////////////////////////////////////////////////////////////////////////////
32
33namespace {
34struct AudioDeviceFormatLess
35{
36 bool operator()(const std::pair<QAudioDevice, QAudioFormat> &lhs,
37 const std::pair<QAudioDevice, QAudioFormat> &rhs) const
38 {
39 auto cmp = qCompareThreeWay(lhs.first.id(), rhs.first.id());
40 if (cmp == Qt::strong_ordering::less)
41 return true;
42 if (cmp == Qt::strong_ordering::greater)
43 return false;
44
45 return std::tuple(lhs.second.sampleRate(), lhs.second.sampleFormat(),
46 lhs.second.channelCount())
47 < std::tuple(rhs.second.sampleRate(), rhs.second.sampleFormat(),
48 rhs.second.channelCount());
49 }
50};
51} // namespace
52
55{
56 if (device.isNull()) {
57 qWarning() << "QRtAudioEngine needs to be called with a valid device";
58 return nullptr;
59 }
60
62 qWarning() << "QRtAudioEngine requires floating point samples";
63 return nullptr;
64 }
65
67 qWarning() << "QRtAudioEngine needs to be called with a supported fromat";
68 return nullptr;
69 }
70
75
77
78 auto key = std::pair{ device, format };
80 if (found != s_playerRegistry.end()) {
81 auto player = found->second.lock();
82 if (player)
83 return player;
84 }
85
86 // lazy clean up
89 });
90
94 });
96
97 return player;
98}
99
142
144{
145 m_sink.reset();
146
147 // consume the ringbuffers
148 m_appToRt.consumeAll([](auto) {
149 });
150 m_rtToApp.consumeAll([](auto) {
151 });
152}
153
155{
156 auto lock = std::lock_guard{ m_mutex };
157
158 // TODO: where do we expect reampling to happen?
160
161 if (m_voices.empty())
162 m_sink.resume();
163
165
167 std::move(voice),
168 });
169}
170
172{
173 stop(voice->voiceId());
174}
175
181
183{
184 auto lock = std::lock_guard{ m_mutex };
185
186 if (visitorIsTrivial) {
188 voiceId,
189 std::move(fn),
190 });
191
192 } else {
194 voiceId,
195 std::move(fn),
196 });
197 }
198}
199
205
207{
210
212
215 };
216
217 for (const SharedVoice &voice : m_rtVoiceRegistry) {
218 Q_ASSERT(voice.use_count() >= 2); // voice in both m_rtVoiceRegistry and m_voices
219
223 }
224
225 if (!finishedVoices.empty()) {
227 for (const SharedVoice &voice : finishedVoices) {
230 if (stopSent)
231 sendNotification = true;
232 }
233 });
234 }
235
236 // TODO: we should probably (soft)clip the output buffer
237
241}
242
244{
245 bool notifyApp = false;
246
247#if __cpp_lib_erase_if >= 202002L
248 using std::erase_if;
249#else
250 auto erase_if = [](auto &c, auto &&pred) {
251 auto old_size = c.size();
252 for (auto first = c.begin(), last = c.end(); first != last;) {
253 if (pred(*first))
254 first = c.erase(first);
255 else
256 ++first;
257 }
258 return old_size - c.size();
259 };
260#endif
263 bool voiceIsActive = voice->isActive();
264 if (!voiceIsActive)
266
267 return !voiceIsActive;
268 });
269 });
270
271 if (notifyApp)
273}
274
276{
278 for (RtCommand &cmd : commands) {
279 std::visit([&](auto cmd) {
281 }, std::move(cmd));
282 }
283 });
284}
285
287{
290 });
291}
292
294{
296 if (it == m_rtVoiceRegistry.end())
297 return;
298
301
303 std::move(voice),
304 });
305 if (emitNotify)
307}
308
310{
312 if (it == m_rtVoiceRegistry.end())
313 return;
314
315 cmd.callback(**it);
316
317 // send callback back to application for destruction
320 });
321 if (emitNotify)
323}
324
326{
328 if (it == m_rtVoiceRegistry.end())
329 return;
330
331 cmd.callback(**it);
332}
333
335{
337 {
338 auto lock = std::lock_guard{ m_mutex };
341 std::visit([&](auto notification) {
343 }, std::move(notification));
344 }
345 });
346
349 }
350
351 // emit voiceFinished outside of the lock
354}
355
357{
359 if (m_voices.empty())
360 m_sink.suspend();
362}
363
365{
366 // nop (just making sure to delete on the application thread);
367}
368
370{
371 // first write all pending commands from overflow buffer
373
374 bool written = m_appToRt.produceOne([&] {
375 return std::move(cmd);
376 });
377
378 if (written)
379 return;
380
382
386 });
387}
388
390{
391 // first write all pending commands from overflow buffer
393
394 bool written = m_rtToApp.produceOne([&] {
395 return std::move(cmd);
396 });
397
398 if (written)
399 return true;
400
402
403 return emitNotification;
404}
405
407{
408 while (!m_appToRtOverflowBuffer.empty()) {
410 bool written = m_appToRt.produceOne([&] {
412 });
413 if (!written)
414 return;
415
417 }
418}
419
421{
422 bool emitNotification = false;
423 while (!m_rtToAppOverflowBuffer.empty()) {
425
426 // first write all pending commands from overflow buffer
427 bool written = m_rtToApp.produceOne([&] {
429 });
430 if (!written)
431 break;
432
434 emitNotification = true;
435 }
436
437 return emitNotification;
438}
439
440} // namespace QtMultimediaPrivate
441
442QT_END_NAMESPACE
443
444#ifdef Q_CC_MINGW
445QT_WARNING_POP
446#endif
447
448#include "moc_qrtaudioengine_p.cpp"