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 delete engine;
95 else
97 });
99
100 return player;
101}
102
145
147{
148 m_sink.reset();
149
150 // consume the ringbuffers
151 m_appToRt.consumeAll([](auto) {
152 });
153 m_rtToApp.consumeAll([](auto) {
154 });
155}
156
158{
159 auto lock = std::lock_guard{ m_mutex };
160
161 // TODO: where do we expect reampling to happen?
163
164 if (m_voices.empty())
165 m_sink.resume();
166
168
170 std::move(voice),
171 });
172}
173
175{
176 stop(voice->voiceId());
177}
178
184
186{
187 auto lock = std::lock_guard{ m_mutex };
188
189 if (visitorIsTrivial) {
191 voiceId,
192 std::move(fn),
193 });
194
195 } else {
197 voiceId,
198 std::move(fn),
199 });
200 }
201}
202
208
210{
213
215
218 };
219
220 for (const SharedVoice &voice : m_rtVoiceRegistry) {
221 Q_ASSERT(voice.use_count() >= 2); // voice in both m_rtVoiceRegistry and m_voices
222
226 }
227
228 if (!finishedVoices.empty()) {
230 for (const SharedVoice &voice : finishedVoices) {
233 if (stopSent)
234 sendNotification = true;
235 }
236 });
237 }
238
239 // TODO: we should probably (soft)clip the output buffer
240
244}
245
247{
248 bool notifyApp = false;
249
250#if __cpp_lib_erase_if >= 202002L
251 using std::erase_if;
252#else
253 auto erase_if = [](auto &c, auto &&pred) {
254 auto old_size = c.size();
255 for (auto first = c.begin(), last = c.end(); first != last;) {
256 if (pred(*first))
257 first = c.erase(first);
258 else
259 ++first;
260 }
261 return old_size - c.size();
262 };
263#endif
266 bool voiceIsActive = voice->isActive();
267 if (!voiceIsActive)
269
270 return !voiceIsActive;
271 });
272 });
273
274 if (notifyApp)
276}
277
279{
281 for (RtCommand &cmd : commands) {
282 std::visit([&](auto cmd) {
284 }, std::move(cmd));
285 }
286 });
287}
288
290{
293 });
294}
295
297{
299 if (it == m_rtVoiceRegistry.end())
300 return;
301
304
306 std::move(voice),
307 });
308 if (emitNotify)
310}
311
313{
315 if (it == m_rtVoiceRegistry.end())
316 return;
317
318 cmd.callback(**it);
319
320 // send callback back to application for destruction
323 });
324 if (emitNotify)
326}
327
329{
331 if (it == m_rtVoiceRegistry.end())
332 return;
333
334 cmd.callback(**it);
335}
336
338{
340 {
341 auto lock = std::lock_guard{ m_mutex };
344 std::visit([&](auto notification) {
346 }, std::move(notification));
347 }
348 });
349
352 }
353
354 // emit voiceFinished outside of the lock
357}
358
360{
362 if (m_voices.empty())
363 m_sink.suspend();
365}
366
368{
369 // nop (just making sure to delete on the application thread);
370}
371
373{
374 // first write all pending commands from overflow buffer
376
377 bool written = m_appToRt.produceOne([&] {
378 return std::move(cmd);
379 });
380
381 if (written)
382 return;
383
385
389 });
390}
391
393{
394 // first write all pending commands from overflow buffer
396
397 bool written = m_rtToApp.produceOne([&] {
398 return std::move(cmd);
399 });
400
401 if (written)
402 return true;
403
405
406 return emitNotification;
407}
408
410{
411 while (!m_appToRtOverflowBuffer.empty()) {
413 bool written = m_appToRt.produceOne([&] {
415 });
416 if (!written)
417 return;
418
420 }
421}
422
424{
425 bool emitNotification = false;
426 while (!m_rtToAppOverflowBuffer.empty()) {
428
429 // first write all pending commands from overflow buffer
430 bool written = m_rtToApp.produceOne([&] {
432 });
433 if (!written)
434 break;
435
437 emitNotification = true;
438 }
439
440 return emitNotification;
441}
442
443} // namespace QtMultimediaPrivate
444
445QT_END_NAMESPACE
446
447#ifdef Q_CC_MINGW
448QT_WARNING_POP
449#endif
450
451#include "moc_qrtaudioengine_p.cpp"