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_p.h
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
4#ifndef QAUDIOSYSTEM_PLATFORM_STREAM_SUPPORT_P_H
5#define QAUDIOSYSTEM_PLATFORM_STREAM_SUPPORT_P_H
6
7//
8// W A R N I N G
9// -------------
10//
11// This file is not part of the Qt API. It exists purely as an
12// implementation detail. This header file may change from version to
13// version without notice, or even be removed.
14//
15// We mean it.
16//
17
18#include <QtMultimedia/private/qaudio_qiodevice_support_p.h>
19#include <QtMultimedia/private/qaudiohelpers_p.h>
20#include <QtMultimedia/private/qaudioringbuffer_p.h>
21#include <QtMultimedia/private/qaudiosystem_p.h>
22#include <QtMultimedia/private/qautoresetevent_p.h>
23#include <QtMultimedia/qaudiodevice.h>
24#include <QtMultimedia/qaudioformat.h>
25#include <QtMultimedia/qtmultimediaglobal.h>
26#include <QtCore/qscopedvaluerollback.h>
27#include <QtCore/qthread.h>
28
29#include <optional>
30#include <variant>
31
32QT_BEGIN_NAMESPACE
33
34namespace QtPrivate {
36}
37
38namespace QtMultimediaPrivate {
39
41{
42 template <typename T>
44
47
48public:
49 static qsizetype inferRingbufferFrames(const std::optional<int> &ringbufferSize,
50 const std::optional<NativePeriodFrames> &nativePeriodFrames,
51 const QAudioFormat &);
52 static qsizetype inferRingbufferBytes(const std::optional<int> &ringbufferSize,
53 const std::optional<NativePeriodFrames> &nativePeriodFrames,
54 const QAudioFormat &);
55
56protected:
59
60 QPlatformAudioIOStream(QAudioDevice m_audioDevice, QAudioFormat m_format,
61 std::optional<int> ringbufferSize,
62 std::optional<NativePeriodFrames> nativePeriodFrames, float volume);
65
66 void setVolume(float);
67 float volume() const { return m_volume.load(std::memory_order_relaxed); }
68
69 template <typename Functor>
70 auto visitRingbuffer(Functor &&f)
71 {
72 return std::visit(f, m_ringbuffer);
73 }
74
75 template <typename Functor>
76 auto visitRingbuffer(Functor &&f) const
77 {
78 return std::visit(f, m_ringbuffer);
79 }
80
81 void prepareRingbuffer(std::optional<int> ringbufferSize);
83
84 // stop requests
85 void requestStop();
86 bool isStopRequested(std::memory_order memory_order = std::memory_order_relaxed) const
87 {
88 return m_stopRequested.load(memory_order);
89 }
90
91 // members
95
96private:
97 std::atomic<float> m_volume{
98 1.f,
99 };
100
101 Ringbuffer m_ringbuffer{
103 0,
104 };
105
106 // stop requests
107 std::atomic<bool> m_stopRequested = false;
108
109public:
115};
116
117////////////////////////////////////////////////////////////////////////////////////////////////////
118
120{
121public:
122 using QPlatformAudioIOStream::ShutdownPolicy;
124
125 using QPlatformAudioIOStream::requestStop;
126
127protected:
128 QPlatformAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional<int> ringbufferSize,
129 std::optional<NativePeriodFrames> nativePeriodFrames, float volume);
132
135
136 // ringbuffer / stats
137 quint64 bytesFree() const;
139
140 // downstream delegates
141 virtual void updateStreamIdle(bool) = 0;
142
143 // iodevice
145 void setQIODevice(QIODevice *device);
146 void createQIODeviceConnections(QIODevice *device);
148 void pullFromQIODevice();
149
150 // LATER: do we want to relax notifying the app thread?
151 static constexpr int notificationThresholdBytes = 0;
152
153 // idle detection
154 void setIdleState(bool);
155 bool isIdle(std::memory_order order = std::memory_order_relaxed) const
156 {
157 return m_streamIsIdle.load(order);
158 }
159 void stopIdleDetection();
160
161 template <typename Functor>
162 auto connectIdleHandler(Functor &&f)
163 {
164 return m_streamIdleDetectionNotifier.callOnActivated(std::forward<Functor>(f));
165 }
166
167 template <typename ParentType>
168 void handleIOError(ParentType *parent)
169 {
170 if (parent) {
171 Q_ASSERT(thread()->isCurrentThread());
172 parent->m_stream = {};
173 parent->setError(QAudio::IOError);
174 parent->updateStreamState(QtAudio::State::StoppedState);
175 }
176 }
177
178 QThread *thread() const;
179
180 template <typename Functor>
181 void invokeOnAppThread(Functor &&f)
182 {
183 // note: this is not a QObject, so we use the first QObject member of the stream as context
184 QMetaObject::invokeMethod(&m_streamIdleDetectionNotifier, std::forward<Functor>(f));
185 }
186
187private:
188 // qiodevice
189 QIODevice *m_device = nullptr;
190
191 // idle detection
192 std::atomic<bool> m_streamIsIdle = false;
193 QAutoResetEvent m_streamIdleDetectionNotifier;
194 QMetaObject::Connection m_streamIdleDetectionConnection;
195
196 // ringbuffer events
197 QAutoResetEvent m_ringbufferHasSpace;
198 QMetaObject::Connection m_ringbufferHasSpaceConnection;
199 QMetaObject::Connection m_iodeviceHasNewDataConnection;
200
201 std::unique_ptr<QtPrivate::QIODeviceRingBufferWriterBase> m_ringbufferWriterDevice;
202
203 // stats
204 std::atomic_int64_t m_totalFrameCount{};
205 std::atomic_int64_t m_processedFrameCount{};
206
207 void convertToNative(QSpan<const std::byte> internal, QSpan<std::byte> native, float volume,
208 NativeSampleFormat) noexcept Q_DECL_NONBLOCKING_FUNCTION;
209
210 // pullFromQIODeviceToRingbuffer is not reentrant. however we might end up in situations where a
211 // QIODevice emits readReady from within QIODevice::readData. We protect against this using a
212 // reentrancy guard and queue invocations if we detect a reentrant call
213 template <typename Functor>
214 void withPullIODeviceReentrancyGuard(Functor f)
215 {
216 if (!m_pullIODeviceReentrancyGuard) {
217 QScopedValueRollback<bool> guard{
218 m_pullIODeviceReentrancyGuard,
219 true,
220 };
221 f();
222 } else {
223 QMetaObject::invokeMethod(&m_streamIdleDetectionNotifier,
224 [this, f = std::move(f)]() mutable {
225 withPullIODeviceReentrancyGuard(std::move(f));
226 }, Qt::QueuedConnection);
227 }
228 }
229 bool m_pullIODeviceReentrancyGuard = false;
230
231 void pullFromQIODeviceImpl();
232};
233
234////////////////////////////////////////////////////////////////////////////////////////////////////
235
237{
238public:
239 using QPlatformAudioIOStream::ShutdownPolicy;
241
242 using QPlatformAudioIOStream::requestStop;
243
244protected:
245 QPlatformAudioSourceStream(QAudioDevice, const QAudioFormat &,
246 std::optional<int> ringbufferSize,
247 std::optional<NativePeriodFrames> nativePeriodFrames, float volume);
249
251
254
255 // ringbuffer / stats
256 qsizetype bytesReady() const;
258
259 // iodevice
260 void setQIODevice(QIODevice *device);
261 void createQIODeviceConnections(QIODevice *device);
264 void pushToIODevice();
265 bool deviceIsRingbufferReader() const;
267 void emptyRingbuffer();
268
269 // downstream delegates
270 virtual void updateStreamIdle(bool) = 0;
271
272 template <typename ParentType>
273 void handleIOError(ParentType *parent)
274 {
275 if (parent) {
276 Q_ASSERT(thread()->isCurrentThread());
277
279 // we own the qiodevice, so let's keep it alive to allow users to drain the
280 // ringbuffer
281 parent->m_retiredStream = std::move(parent->m_stream);
282 else
283 parent->m_stream = {};
284
285 parent->setError(QAudio::IOError);
286 parent->updateStreamState(QtAudio::State::StoppedState);
287 }
288 }
289
290 QThread *thread() const;
291
292 template <typename Functor>
293 void invokeOnAppThread(Functor &&f)
294 {
295 // note: this is not a QObject, so we use the first QObject member of the stream as context
296 QMetaObject::invokeMethod(&m_ringbufferHasData, std::forward<Functor>(f));
297 }
298
299private:
300 // qiodevice
301 QIODevice *m_device = nullptr;
302 std::unique_ptr<QIODevice> m_ringbufferReaderDevice;
303
304 // ringbuffer events
305 QAutoResetEvent m_ringbufferHasData;
306 QAutoResetEvent m_ringbufferIsFull;
307
308 QMetaObject::Connection m_ringbufferHasDataConnection;
309 QMetaObject::Connection m_ringbufferIsFullConnection;
310
311 // stats
312 std::atomic_uint64_t m_totalNumberOfFramesPushedToRingbuffer{};
313
314 void convertFromNative(QSpan<const std::byte> native, QSpan<std::byte> internal, float volume,
315 NativeSampleFormat) noexcept Q_DECL_NONBLOCKING_FUNCTION;
316};
317
318} // namespace QtMultimediaPrivate
319
320QT_END_NAMESPACE
321
322#endif // QAUDIOSYSTEM_PLATFORM_STREAM_SUPPORT_P_H
bool isStopRequested(std::memory_order memory_order=std::memory_order_relaxed) const
QPlatformAudioIOStream(QAudioDevice m_audioDevice, QAudioFormat m_format, std::optional< int > ringbufferSize, std::optional< NativePeriodFrames > nativePeriodFrames, float volume)
const std::optional< NativePeriodFrames > m_nativePeriodFrames
static qsizetype inferRingbufferFrames(const std::optional< int > &ringbufferSize, const std::optional< NativePeriodFrames > &nativePeriodFrames, const QAudioFormat &)
static qsizetype inferRingbufferBytes(const std::optional< int > &ringbufferSize, const std::optional< NativePeriodFrames > &nativePeriodFrames, const QAudioFormat &)
void prepareRingbuffer(std::optional< int > ringbufferSize)
QPlatformAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< int > ringbufferSize, std::optional< NativePeriodFrames > nativePeriodFrames, float volume)
bool isIdle(std::memory_order order=std::memory_order_relaxed) const
QPlatformAudioSourceStream(QAudioDevice, const QAudioFormat &, std::optional< int > ringbufferSize, std::optional< NativePeriodFrames > nativePeriodFrames, float volume)
Combined button and popup list for selecting options.