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#include <QtMultimedia/private/qmultimedia_assume_p.h>
10
11#include <stdlib.h>
12#if __has_include(<alloca.h>)
13# include <alloca.h>
14#endif
15#if __has_include(<malloc.h>)
16# include <malloc.h>
17#endif
18
19#ifdef Q_CC_MSVC
20# define alloca _alloca
21#endif
22
23QT_BEGIN_NAMESPACE
24
25namespace QtMultimediaPrivate {
26
27using namespace std::chrono_literals;
28
29QPlatformAudioIOStream::QPlatformAudioIOStream(QAudioDevice m_audioDevice, QAudioFormat m_format,
30 std::optional<int> ringbufferSize,
31 std::optional<int32_t> hardwareBufferFrames,
32 float volume)
35 },
38 },
41 },
43 volume,
44 }
45{
46 prepareRingbuffer(ringbufferSize);
47}
48
50{
51 Q_ASSERT(m_stopRequested);
52}
53
54void QPlatformAudioIOStream::setVolume(float volume)
55{
56 m_volume.store(volume, std::memory_order_relaxed);
57}
58
59void QPlatformAudioIOStream::prepareRingbuffer(std::optional<int> ringbufferSize)
60{
61 using SampleFormat = QAudioFormat::SampleFormat;
62
63 // Warning: QAudioSink::setBufferSize is measured in *bytes* not in *frames*
64 int ringbufferElements = inferRingbufferFrames(ringbufferSize, m_hardwareBufferFrames, m_format)
65 * m_format.channelCount();
66
67 switch (m_format.sampleFormat()) {
68 case SampleFormat::Float:
69 m_ringbuffer.emplace<QAudioRingBuffer<float>>(ringbufferElements);
70 break;
71 case SampleFormat::Int16:
72 m_ringbuffer.emplace<QAudioRingBuffer<int16_t>>(ringbufferElements);
73 break;
74 case SampleFormat::Int32:
75 m_ringbuffer.emplace<QAudioRingBuffer<int32_t>>(ringbufferElements);
76 break;
77 case SampleFormat::UInt8:
78 m_ringbuffer.emplace<QAudioRingBuffer<uint8_t>>(ringbufferElements);
79 break;
80
81 default:
82 qCritical() << "invalid sample format";
83 Q_UNREACHABLE_RETURN();
84 }
85}
86
88{
89 m_stopRequested.store(true, std::memory_order_release);
90}
91
92qsizetype
93QPlatformAudioIOStream::inferRingbufferFrames(const std::optional<int> &ringbufferSize,
94 const std::optional<int32_t> &hardwareBufferFrames,
95 const QAudioFormat &format)
96{
97 int bytesPerFrame = format.bytesPerFrame();
98 QT_MM_ASSUME(bytesPerFrame > 0);
99
100 return inferRingbufferBytes(ringbufferSize, hardwareBufferFrames, format) / bytesPerFrame;
101}
102
103qsizetype
104QPlatformAudioIOStream::inferRingbufferBytes(const std::optional<int> &ringbufferSize,
105 const std::optional<int32_t> &hardwareBufferFrames,
106 const QAudioFormat &format)
107{
108 // ensure to a sane minimum ringbuffer size of twice the hw buffer size or 32 frames
109 const int minimumRingbufferFrames = hardwareBufferFrames ? *hardwareBufferFrames * 2 : 32;
110 const int minimumRingbufferBytes = format.bytesForFrames(minimumRingbufferFrames);
111 if (ringbufferSize)
112 return ringbufferSize >= minimumRingbufferBytes ? *ringbufferSize : minimumRingbufferBytes;
113
114 using namespace std::chrono;
115 static constexpr auto defaultBufferDuration = 250ms;
116
117 return format.bytesForDuration(microseconds(defaultBufferDuration).count());
118}
119
121{
122 return visitRingbuffer([](auto &ringbuffer) {
123 using SampleType = typename std::decay_t<decltype(ringbuffer)>::ValueType;
124 return ringbuffer.size() * sizeof(SampleType);
125 });
126}
127
128////////////////////////////////////////////////////////////////////////////////////////////////////
129
131 const QAudioFormat &format,
132 std::optional<int> ringbufferSize,
133 std::optional<int32_t> hardwareBufferFrames,
134 float volume)
136 std::move(audioDevice), format, ringbufferSize, hardwareBufferFrames, volume,
138{
139 m_streamIdleDetectionConnection = m_streamIdleDetectionNotifier.callOnActivated([this] {
140 if (isStopRequested())
141 return;
142
143 bool sinkIsIdle = m_streamIsIdle.load();
144
145 if (sinkIsIdle) {
146 // data has been pushed to the ringbuffer, while the stream is
147 // still idle, this will change during the next audio callback
148 bool ringbufferIsEmpty = visitRingbuffer([&](auto &ringbuffer) {
149 return ringbuffer.free() == ringbuffer.size();
150 });
151
152 sinkIsIdle = ringbufferIsEmpty;
153 }
154
155 updateStreamIdle(sinkIsIdle);
156 });
157}
158
160
164{
166
167 const float vol = volume();
168
171 if (nativeFormat) {
172 // Amount of bytes in output range differ from ringbuffer range
175
179 } else {
184 }
185 });
186 });
187
191 }
192
193 if (!isStopRequested()) {
196
199 m_streamIsIdle.store(false);
202 m_streamIsIdle.store(true);
204 }
205 }
206 if (!hostBuffer.empty())
208
212
213 return consumedFrames;
214}
215
217{
218 return visitRingbuffer([](auto &ringbuffer) {
219 using SampleType = typename std::decay_t<decltype(ringbuffer)>::ValueType;
220 return ringbuffer.free() * sizeof(SampleType);
221 });
222}
223
230
237
239{
243
244 visitRingbuffer([&](auto &ringbuffer) {
246 if (elementsPulled)
247 updateStreamIdle(false);
248 });
249}
250
252{
253 // consumed from audio thread
256 });
257
258 // data has been pushed to device
264 });
265 });
266}
267
273
284
289
294
299
301{
302 // QPlatformAudioSinkStream is not a QObject, but still has a notion of an application thread
303 // where it lives on.
305}
306
307// we limit alloca calls to 0.5MB. it's good enough for virtually all use cases (i.e. buffers
308// of 4092 frames / 32 channels) and well in the reasonable range of available stack memory on linux
309// (8MB)
310static constexpr qsizetype scratchpadBufferSizeLimit = 512 * 1024;
311static_assert(scratchpadBufferSizeLimit > 4092 * 32 * sizeof(float));
312
314 QSpan<std::byte> native, float volume,
316{
317 using namespace QAudioHelperInternal;
318
319 if (volume == 1.f) {
322 return;
323 }
324
326 std::byte *scratchpadMemory = reinterpret_cast<std::byte *>(alloca(internal.size()));
328
332}
333
334////////////////////////////////////////////////////////////////////////////////////////////////////
335
346
348
352{
354
355 const float vol = volume();
356 using namespace QtMultimediaPrivate;
357
359 using SampleType = typename std::decay_t<decltype(rb)>::ValueType;
360
361 // clang-format off
363 if (nativeFormat) {
364 // Amount of bytes in input range differ from ringbuffer range
367
371 *nativeFormat);
372 } else {
373 QSpan<const std::byte> inputByteRange =
378 }
379 return ringbufferRange;
381 // clang-format on
382 });
383
386
389 return framesWritten;
390}
391
403
408
410{
411 switch (shutdownPolicy) {
413 return;
417 return;
418
419 default:
421 }
422}
423
425{
426 visitRingbuffer([](auto &ringbuffer) {
427 ringbuffer.consumeAll([](auto &) {
428 });
429 });
430}
431
433{
434 // QPlatformAudioSourceStream is not a QObject, but still has a notion of an application thread
435 // where it lives on.
437}
438
440{
441 return visitRingbuffer([](const auto &ringbuffer) {
442 return ringbuffer.used() * sizeof(typename std::decay_t<decltype(ringbuffer)>::ValueType);
443 });
444}
445
453
458
482
488
502
504 QSpan<const std::byte> native, QSpan<std::byte> internal, float volume,
506{
507 using namespace QAudioHelperInternal;
508 if (volume == 1.f) {
511 return;
512 }
513
515 std::byte *scratchpadMemory = reinterpret_cast<std::byte *>(alloca(internal.size()));
517
520
522}
523
524} // namespace QtMultimediaPrivate
525
526QT_END_NAMESPACE
527
528#ifdef Q_CC_MSVC
529# undef alloca
530#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)
#define __has_include(x)