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 <QtCore/qdebug.h>
7#include <QtMultimedia/private/qaudiohelpers_p.h>
8#include <QtMultimedia/private/qaudio_qiodevice_support_p.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<int32_t> hardwareBufferFrames,
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_hardwareBufferFrames, 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<int32_t> &hardwareBufferFrames,
82 const QAudioFormat &format)
83{
84 int bytesPerFrame = format.bytesPerFrame();
85 Q_PRESUME(bytesPerFrame > 0);
87 return inferRingbufferBytes(ringbufferSize, hardwareBufferFrames, format) / bytesPerFrame;
88}
89
90qsizetype
91QPlatformAudioIOStream::inferRingbufferBytes(const std::optional<int> &ringbufferSize,
92 const std::optional<int32_t> &hardwareBufferFrames,
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 = hardwareBufferFrames ? *hardwareBufferFrames * 2 : 32;
97 const int minimumRingbufferBytes = format.bytesForFrames(minimumRingbufferFrames);
98 if (ringbufferSize)
99 return ringbufferSize >= minimumRingbufferBytes ? *ringbufferSize : minimumRingbufferBytes;
100
101 using namespace std::chrono;
102 static constexpr auto defaultBufferDuration = 250ms;
103
104 return format.bytesForDuration(microseconds(defaultBufferDuration).count());
105}
106
108{
109 return visitRingbuffer([](auto &ringbuffer) {
110 using SampleType = typename std::decay_t<decltype(ringbuffer)>::ValueType;
111 return ringbuffer.size() * sizeof(SampleType);
112 });
113}
114
115////////////////////////////////////////////////////////////////////////////////////////////////////
116
118 const QAudioFormat &format,
119 std::optional<int> ringbufferSize,
120 std::optional<int32_t> hardwareBufferFrames,
121 float volume)
123 std::move(audioDevice), format, ringbufferSize, hardwareBufferFrames, volume,
124 }
125{
126 m_streamIdleDetectionConnection = m_streamIdleDetectionNotifier.callOnActivated([this] {
127 if (isStopRequested())
128 return;
129
130 bool sinkIsIdle = m_streamIsIdle.load();
131
132 if (sinkIsIdle) {
133 // data has been pushed to the ringbuffer, while the stream is
134 // still idle, this will change during the next audio callback
135 bool ringbufferIsEmpty = visitRingbuffer([&](auto &ringbuffer) {
136 return ringbuffer.free() == ringbuffer.size();
137 });
139 sinkIsIdle = ringbufferIsEmpty;
140 }
141
142 updateStreamIdle(sinkIsIdle);
143 });
144}
145
147
151{
153
154 const float vol = volume();
155
158 if (nativeFormat) {
159 // Amount of bytes in output range differ from ringbuffer range
162
166 } else {
171 }
172 });
173 });
174
178 }
179
180 if (!isStopRequested()) {
183
186 m_streamIsIdle.store(false);
189 m_streamIsIdle.store(true);
191 }
192 }
193 if (!hostBuffer.empty())
195
199
200 return consumedFrames;
201}
202
204{
205 return visitRingbuffer([](auto &ringbuffer) {
206 using SampleType = typename std::decay_t<decltype(ringbuffer)>::ValueType;
207 return ringbuffer.free() * sizeof(SampleType);
208 });
209}
210
217
224
226{
230
231 visitRingbuffer([&](auto &ringbuffer) {
233 if (elementsPulled)
234 updateStreamIdle(false);
235 });
236}
237
239{
240 // consumed from audio thread
243 });
244
245 // data has been pushed to device
250 updateStreamIdle(false);
251 });
252 });
253}
254
256{
259}
260
262{
265 using SampleType = typename std::decay_t<decltype(ringbuffer)>::ValueType;
267 });
268
270}
271
276
281
286
288{
289 // QPlatformAudioSinkStream is not a QObject, but still has a notion of an application thread
290 // where it lives on.
292}
293
294// we limit alloca calls to 0.5MB. it's good enough for virtually all use cases (i.e. buffers
295// of 4092 frames / 32 channels) and well in the reasonable range of available stack memory on linux
296// (8MB)
297static constexpr qsizetype scratchpadBufferSizeLimit = 512 * 1024;
298static_assert(scratchpadBufferSizeLimit > 4092 * 32 * sizeof(float));
299
301 QSpan<std::byte> native, float volume,
303{
304 using namespace QAudioHelperInternal;
305
306 if (volume == 1.f) {
309 return;
310 }
311
317 });
318}
319
320////////////////////////////////////////////////////////////////////////////////////////////////////
321
332
334
338{
340
341 const float vol = volume();
342 using namespace QtMultimediaPrivate;
343
345 using SampleType = typename std::decay_t<decltype(rb)>::ValueType;
346
347 // clang-format off
349 if (nativeFormat) {
350 // Amount of bytes in input range differ from ringbuffer range
353
357 *nativeFormat);
358 } else {
359 QSpan<const std::byte> inputByteRange =
364 }
365 return ringbufferRange;
367 // clang-format on
368 });
369
372
375 return framesWritten;
376}
377
389
394
396{
397 switch (shutdownPolicy) {
399 return;
403 return;
404
405 default:
407 }
408}
409
411{
412 visitRingbuffer([](auto &ringbuffer) {
413 ringbuffer.consumeAll([](auto &) {
414 });
415 });
416}
417
419{
420 // QPlatformAudioSourceStream is not a QObject, but still has a notion of an application thread
421 // where it lives on.
423}
424
426{
427 return visitRingbuffer([](const auto &ringbuffer) {
428 return ringbuffer.used() * sizeof(typename std::decay_t<decltype(ringbuffer)>::ValueType);
429 });
430}
431
439
444
468
474
488
490 QSpan<const std::byte> native, QSpan<std::byte> internal, float volume,
492{
493 using namespace QAudioHelperInternal;
494 if (volume == 1.f) {
497 return;
498 }
499
505 });
506}
507
508} // namespace QtMultimediaPrivate
509
510QT_END_NAMESPACE
511
512#ifdef Q_CC_MSVC
513# undef alloca
514#endif
QPlatformAudioIOStream(QAudioDevice m_audioDevice, QAudioFormat m_format, std::optional< int > ringbufferSize, std::optional< int32_t > hardwareBufferFrames, float volume)
void prepareRingbuffer(std::optional< int > ringbufferSize)
QPlatformAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< int > ringbufferSize, std::optional< int32_t > hardwareBufferFrames, float volume)
Combined button and popup list for selecting options.