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
qaudiosystem_platform_stream_support.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/qaudio_qiodevice_support_p.h>
7#include <QtMultimedia/private/qaudiohelpers_p.h>
8#include <QtCore/qdebug.h>
9
11
12namespace QtMultimediaPrivate {
13
14using namespace std::chrono_literals;
15
16QPlatformAudioIOStream::QPlatformAudioIOStream(QAudioDevice m_audioDevice, QAudioFormat m_format,
17 std::optional<int> ringbufferSize,
18 std::optional<NativePeriodFrames> nativePeriodFrames,
19 float volume)
22 },
25 },
28 },
30 volume,
31 }
32{
33 prepareRingbuffer(ringbufferSize);
34}
35
37{
38 Q_ASSERT(m_stopRequested);
39}
40
41void QPlatformAudioIOStream::setVolume(float volume)
42{
43 m_volume.store(volume, std::memory_order_relaxed);
44}
45
46void QPlatformAudioIOStream::prepareRingbuffer(std::optional<int> ringbufferSize)
47{
48 using SampleFormat = QAudioFormat::SampleFormat;
49
50 // Warning: QAudioSink::setBufferSize is measured in *bytes* not in *frames*
51 int ringbufferElements = inferRingbufferFrames(ringbufferSize, m_nativePeriodFrames, m_format)
52 * m_format.channelCount();
53
54 switch (m_format.sampleFormat()) {
55 case SampleFormat::Float:
56 m_ringbuffer.emplace<QAudioRingBuffer<float>>(ringbufferElements);
57 break;
58 case SampleFormat::Int16:
59 m_ringbuffer.emplace<QAudioRingBuffer<int16_t>>(ringbufferElements);
60 break;
61 case SampleFormat::Int32:
62 m_ringbuffer.emplace<QAudioRingBuffer<int32_t>>(ringbufferElements);
63 break;
64 case SampleFormat::UInt8:
65 m_ringbuffer.emplace<QAudioRingBuffer<uint8_t>>(ringbufferElements);
66 break;
67
68 default:
69 qCritical() << "invalid sample format";
70 Q_UNREACHABLE_RETURN();
71 }
72}
73
75{
76 m_stopRequested.store(true, std::memory_order_release);
77}
78
79qsizetype
80QPlatformAudioIOStream::inferRingbufferFrames(const std::optional<int> &ringbufferSize,
81 const std::optional<NativePeriodFrames> &nativePeriodFrames,
82 const QAudioFormat &format)
83{
84 int bytesPerFrame = format.bytesPerFrame();
85 Q_PRESUME(bytesPerFrame > 0);
87 return inferRingbufferBytes(ringbufferSize, nativePeriodFrames, format) / bytesPerFrame;
88}
89
90qsizetype
91QPlatformAudioIOStream::inferRingbufferBytes(const std::optional<int> &ringbufferSize,
92 const std::optional<NativePeriodFrames> &nativePeriodFrames,
93 const QAudioFormat &format)
94{
95 // ensure to a sane minimum ringbuffer size of twice the hw buffer size or 32 frames
96 const int minimumRingbufferFrames = nativePeriodFrames
97 ? qToUnderlying(*nativePeriodFrames) * 2
98 : 32;
99 const int minimumRingbufferBytes = format.bytesForFrames(minimumRingbufferFrames);
100 if (ringbufferSize)
101 return ringbufferSize >= minimumRingbufferBytes ? *ringbufferSize : minimumRingbufferBytes;
102
103 using namespace std::chrono;
104 static constexpr auto defaultBufferDuration = 250ms;
105
106 return format.bytesForDuration(microseconds(defaultBufferDuration).count());
107}
108
110{
111 return visitRingbuffer([](auto &ringbuffer) {
112 using SampleType = typename std::decay_t<decltype(ringbuffer)>::ValueType;
113 return ringbuffer.size() * sizeof(SampleType);
114 });
115}
116
117////////////////////////////////////////////////////////////////////////////////////////////////////
118
120 const QAudioFormat &format,
121 std::optional<int> ringbufferSize,
122 std::optional<NativePeriodFrames> nativePeriodFrames,
123 float volume)
125 std::move(audioDevice), format, ringbufferSize, nativePeriodFrames, volume,
126 }
127{
128 m_streamIdleDetectionConnection = m_streamIdleDetectionNotifier.callOnActivated([this] {
129 if (isStopRequested())
130 return;
131
132 bool sinkIsIdle = m_streamIsIdle.load();
133
134 if (sinkIsIdle) {
135 // data has been pushed to the ringbuffer, while the stream is
136 // still idle, this will change during the next audio callback
137 bool ringbufferIsEmpty = visitRingbuffer([&](auto &ringbuffer) {
138 return ringbuffer.free() == ringbuffer.size();
139 });
140
141 sinkIsIdle = ringbufferIsEmpty;
142 }
143
144 updateStreamIdle(sinkIsIdle);
145 });
146}
147
149
153{
155
156 const float vol = volume();
157
160 if (nativeFormat) {
161 // Amount of bytes in output range differ from ringbuffer range
164
168 } else {
173 }
174 });
175 });
176
180 }
181
182 if (!isStopRequested()) {
185
188 m_streamIsIdle.store(false);
191 m_streamIsIdle.store(true);
193 }
194 }
195 if (!hostBuffer.empty()) {
196 if (nativeFormat)
198 else
200 }
201
205
206 return consumedFrames;
207}
208
210{
211 return visitRingbuffer([](auto &ringbuffer) {
212 using SampleType = typename std::decay_t<decltype(ringbuffer)>::ValueType;
213 return ringbuffer.free() * sizeof(SampleType);
214 });
215}
216
223
230
232{
236
237 visitRingbuffer([&](auto &ringbuffer) {
239 if (elementsPulled)
240 updateStreamIdle(false);
241 });
242}
243
245{
246 // consumed from audio thread
249 });
250
251 // data has been pushed to device
256 updateStreamIdle(false);
257 });
258 });
259}
260
277
282
287
292
294{
295 // QPlatformAudioSinkStream is not a QObject, but still has a notion of an application thread
296 // where it lives on.
298}
299
300// we limit alloca calls to 0.5MB. it's good enough for virtually all use cases (i.e. buffers
301// of 4092 frames / 32 channels) and well in the reasonable range of available stack memory on linux
302// (8MB)
303static constexpr qsizetype scratchpadBufferSizeLimit = 512 * 1024;
304static_assert(scratchpadBufferSizeLimit > 4092 * 32 * sizeof(float));
305
307 QSpan<std::byte> native, float volume,
309{
310 using namespace QAudioHelperInternal;
311
312 if (volume == 1.f) {
315 return;
316 }
317
323 });
324}
325
326////////////////////////////////////////////////////////////////////////////////////////////////////
327
338
340
344{
346
347 const float vol = volume();
348 using namespace QtMultimediaPrivate;
349
351 using SampleType = typename std::decay_t<decltype(rb)>::ValueType;
352
353 // clang-format off
355 if (nativeFormat) {
356 // Amount of bytes in input range differ from ringbuffer range
359
363 *nativeFormat);
364 } else {
365 QSpan<const std::byte> inputByteRange =
370 }
371 return ringbufferRange;
373 // clang-format on
374 });
375
378
381 return framesWritten;
382}
383
395
400
402{
403 switch (shutdownPolicy) {
405 return;
409 return;
410
411 default:
413 }
414}
415
417{
418 visitRingbuffer([](auto &ringbuffer) {
419 ringbuffer.consumeAll([](auto &) {
420 });
421 });
422}
423
425{
426 // QPlatformAudioSourceStream is not a QObject, but still has a notion of an application thread
427 // where it lives on.
429}
430
432{
433 return visitRingbuffer([](const auto &ringbuffer) {
434 return ringbuffer.used() * sizeof(typename std::decay_t<decltype(ringbuffer)>::ValueType);
435 });
436}
437
445
450
474
480
494
496 QSpan<const std::byte> native, QSpan<std::byte> internal, float volume,
498{
499 using namespace QAudioHelperInternal;
500 if (volume == 1.f) {
503 return;
504 }
505
511 });
512}
513
514} // namespace QtMultimediaPrivate
515
516QT_END_NAMESPACE
517
518#ifdef Q_CC_MSVC
519# undef alloca
520#endif
QPlatformAudioIOStream(QAudioDevice m_audioDevice, QAudioFormat m_format, std::optional< int > ringbufferSize, std::optional< NativePeriodFrames > nativePeriodFrames, float volume)
void prepareRingbuffer(std::optional< int > ringbufferSize)
QPlatformAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< int > ringbufferSize, std::optional< NativePeriodFrames > nativePeriodFrames, float volume)
Combined button and popup list for selecting options.