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
139
141{
142 m_sink.reset();
143
144 // consume the ringbuffers
145 m_appToRt.consumeAll([](auto) {
146 });
147 m_rtToApp.consumeAll([](auto) {
148 });
149}
150
152{
153 auto lock = std::lock_guard{ m_mutex };
154
155 // TODO: where do we expect reampling to happen?
157
158 if (m_voices.empty())
159 m_sink.resume();
160
162
164 std::move(voice),
165 });
166}
167
169{
170 stop(voice->voiceId());
171}
172
178
180{
181 auto lock = std::lock_guard{ m_mutex };
182
183 if (visitorIsTrivial) {
185 voiceId,
186 std::move(fn),
187 });
188
189 } else {
191 voiceId,
192 std::move(fn),
193 });
194 }
195}
196
202
204{
207
209
212 };
213
214 for (const SharedVoice &voice : m_rtVoiceRegistry) {
215 Q_ASSERT(voice.use_count() >= 2); // voice in both m_rtVoiceRegistry and m_voices
216
220 }
221
222 if (!finishedVoices.empty()) {
224 for (const SharedVoice &voice : finishedVoices) {
227 if (stopSent)
228 sendNotification = true;
229 }
230 });
231 }
232
233 // TODO: we should probably (soft)clip the output buffer
234
238}
239
241{
242 bool notifyApp = false;
243
244#if __cpp_lib_erase_if >= 202002L
245 using std::erase_if;
246#else
247 auto erase_if = [](auto &c, auto &&pred) {
248 auto old_size = c.size();
249 for (auto first = c.begin(), last = c.end(); first != last;) {
250 if (pred(*first))
251 first = c.erase(first);
252 else
253 ++first;
254 }
255 return old_size - c.size();
256 };
257#endif
260 bool voiceIsActive = voice->isActive();
261 if (!voiceIsActive)
263
264 return false;
265 });
266 });
267
268 if (notifyApp)
270}
271
273{
275 for (RtCommand &cmd : commands) {
276 std::visit([&](auto cmd) {
278 }, std::move(cmd));
279 }
280 });
281}
282
284{
287 });
288}
289
291{
293 if (it == m_rtVoiceRegistry.end())
294 return;
295
298
300 std::move(voice),
301 });
302 if (emitNotify)
304}
305
307{
309 if (it == m_rtVoiceRegistry.end())
310 return;
311
312 cmd.callback(**it);
313
314 // send callback back to application for destruction
317 });
318 if (emitNotify)
320}
321
323{
325 if (it == m_rtVoiceRegistry.end())
326 return;
327
328 cmd.callback(**it);
329}
330
332{
334 {
335 auto lock = std::lock_guard{ m_mutex };
338 std::visit([&](auto notification) {
340 }, std::move(notification));
341 }
342 });
343
346 }
347
348 // emit voiceFinished outside of the lock
351}
352
354{
356 if (m_voices.empty())
357 m_sink.suspend();
359}
360
362{
363 // nop (just making sure to delete on the application thread);
364}
365
367{
368 // first write all pending commands from overflow buffer
370
371 bool written = m_appToRt.produceOne([&] {
372 return std::move(cmd);
373 });
374
375 if (written)
376 return;
377
379
383 });
384}
385
387{
388 // first write all pending commands from overflow buffer
390
391 bool written = m_rtToApp.produceOne([&] {
392 return std::move(cmd);
393 });
394
395 if (written)
396 return true;
397
399
400 return emitNotification;
401}
402
404{
405 while (!m_appToRtOverflowBuffer.empty()) {
407 bool written = m_appToRt.produceOne([&] {
409 });
410 if (!written)
411 return;
412
414 }
415}
416
418{
419 bool emitNotification = false;
420 while (!m_rtToAppOverflowBuffer.empty()) {
422
423 // first write all pending commands from overflow buffer
424 bool written = m_rtToApp.produceOne([&] {
426 });
427 if (!written)
428 break;
429
431 emitNotification = true;
432 }
433
434 return emitNotification;
435}
436
437} // namespace QtMultimediaPrivate
438
439QT_END_NAMESPACE
440
441#ifdef Q_CC_MINGW
442QT_WARNING_POP
443#endif
444
445#include "moc_qrtaudioengine_p.cpp"