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/qtmultimediaglobal.h>
19#include <QtMultimedia/qaudioformat.h>
20#include <QtMultimedia/qaudiodevice.h>
21#include <QtMultimedia/private/qautoresetevent_p.h>
22#include <QtMultimedia/private/qaudio_qiodevice_support_p.h>
23#include <QtMultimedia/private/qaudio_rtsan_support_p.h>
24#include <QtMultimedia/private/qaudiosystem_p.h>
25#include <QtMultimedia/private/qaudiohelpers_p.h>
26#include <QtMultimedia/private/qaudioringbuffer_p.h>
27#include <QtCore/qscopedvaluerollback.h>
28#include <QtCore/qthread.h>
29
30#include <optional>
31#include <variant>
32
33QT_BEGIN_NAMESPACE
34
35namespace QtPrivate {
37}
38
39namespace QtMultimediaPrivate {
40
42{
43 template <typename T>
45
48
49public:
50 static qsizetype inferRingbufferFrames(const std::optional<int> &ringbufferSize,
51 const std::optional<int32_t> &hardwareBufferFrames,
52 const QAudioFormat &);
53 static qsizetype inferRingbufferBytes(const std::optional<int> &ringbufferSize,
54 const std::optional<int32_t> &hardwareBufferFrames,
55 const QAudioFormat &);
56
57protected:
60
61 QPlatformAudioIOStream(QAudioDevice m_audioDevice, QAudioFormat m_format,
62 std::optional<int> ringbufferSize,
63 std::optional<int32_t> hardwareBufferFrames, float volume);
66
67 void setVolume(float);
68 float volume() const { return m_volume.load(std::memory_order_relaxed); }
69
70 template <typename Functor>
71 auto visitRingbuffer(Functor &&f)
72 {
73 return std::visit(f, m_ringbuffer);
74 }
75
76 template <typename Functor>
77 auto visitRingbuffer(Functor &&f) const
78 {
79 return std::visit(f, m_ringbuffer);
80 }
81
82 void prepareRingbuffer(std::optional<int> ringbufferSize);
84
85 // stop requests
86 void requestStop();
87 bool isStopRequested(std::memory_order memory_order = std::memory_order_relaxed) const
88 {
89 return m_stopRequested.load(memory_order);
90 }
91
92 // members
96
97private:
98 std::atomic<float> m_volume{
99 1.f,
100 };
101
102 Ringbuffer m_ringbuffer{
104 0,
105 };
106
107 // stop requests
108 std::atomic<bool> m_stopRequested = false;
109
110public:
116};
117
118////////////////////////////////////////////////////////////////////////////////////////////////////
119
121{
122public:
123 using QPlatformAudioIOStream::ShutdownPolicy;
125
126protected:
127 QPlatformAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional<int> ringbufferSize,
128 std::optional<int32_t> hardwareBufferFrames, float volume);
131
134
135 // ringbuffer / stats
136 quint64 bytesFree() const;
138
139 // downstream delegates
140 virtual void updateStreamIdle(bool) = 0;
141
142 // iodevice
144 void setQIODevice(QIODevice *device);
145 void createQIODeviceConnections(QIODevice *device);
147 void pullFromQIODevice();
148
149 // LATER: do we want to relax notifying the app thread?
150 static constexpr int notificationThresholdBytes = 0;
151
152 // idle detection
153 void setIdleState(bool);
154 bool isIdle(std::memory_order order = std::memory_order_relaxed) const
155 {
156 return m_streamIsIdle.load(order);
157 }
158 void stopIdleDetection();
159
160 template <typename Functor>
161 auto connectIdleHandler(Functor &&f)
162 {
163 return m_streamIdleDetectionNotifier.callOnActivated(std::forward<Functor>(f));
164 }
165
166 template <typename ParentType>
167 void handleIOError(ParentType *parent)
168 {
169 if (parent) {
170 Q_ASSERT(thread()->isCurrentThread());
171 parent->setError(QAudio::IOError);
172 parent->updateStreamState(QtAudio::State::StoppedState);
173
174 parent->m_stream = {};
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 QT_MM_NONBLOCKING;
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
242protected:
243 QPlatformAudioSourceStream(QAudioDevice, const QAudioFormat &,
244 std::optional<int> ringbufferSize,
245 std::optional<int32_t> hardwareBufferFrames, float volume);
247
249
252
253 // ringbuffer / stats
254 qsizetype bytesReady() const;
256
257 // iodevice
258 void setQIODevice(QIODevice *device);
259 void createQIODeviceConnections(QIODevice *device);
262 void pushToIODevice();
263 bool deviceIsRingbufferReader() const;
265 void emptyRingbuffer();
266
267 // downstream delegates
268 virtual void updateStreamIdle(bool) = 0;
269
270 template <typename ParentType>
271 void handleIOError(ParentType *parent)
272 {
273 if (parent) {
274 Q_ASSERT(thread()->isCurrentThread());
275 parent->setError(QAudio::IOError);
276 parent->updateStreamState(QtAudio::State::StoppedState);
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 }
286
287 QThread *thread() const;
288
289 template <typename Functor>
290 void invokeOnAppThread(Functor &&f)
291 {
292 // note: this is not a QObject, so we use the first QObject member of the stream as context
293 QMetaObject::invokeMethod(&m_ringbufferHasData, std::forward<Functor>(f));
294 }
295
296private:
297 // qiodevice
298 QIODevice *m_device = nullptr;
299 std::unique_ptr<QIODevice> m_ringbufferReaderDevice;
300
301 // ringbuffer events
302 QAutoResetEvent m_ringbufferHasData;
303 QAutoResetEvent m_ringbufferIsFull;
304
305 QMetaObject::Connection m_ringbufferHasDataConnection;
306 QMetaObject::Connection m_ringbufferIsFullConnection;
307
308 // stats
309 std::atomic_uint64_t m_totalNumberOfFramesPushedToRingbuffer{};
310
311 void convertFromNative(QSpan<const std::byte> native, QSpan<std::byte> internal, float volume,
312 NativeSampleFormat) noexcept QT_MM_NONBLOCKING;
313};
314
315} // namespace QtMultimediaPrivate
316
317QT_END_NAMESPACE
318
319#endif // QAUDIOSYSTEM_PLATFORM_STREAM_SUPPORT_P_H
QPlatformAudioIOStream(QAudioDevice m_audioDevice, QAudioFormat m_format, std::optional< int > ringbufferSize, std::optional< int32_t > hardwareBufferFrames, float volume)
bool isStopRequested(std::memory_order memory_order=std::memory_order_relaxed) const
static qsizetype inferRingbufferFrames(const std::optional< int > &ringbufferSize, const std::optional< int32_t > &hardwareBufferFrames, const QAudioFormat &)
static qsizetype inferRingbufferBytes(const std::optional< int > &ringbufferSize, const std::optional< int32_t > &hardwareBufferFrames, const QAudioFormat &)
void prepareRingbuffer(std::optional< int > ringbufferSize)
QPlatformAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional< int > ringbufferSize, std::optional< int32_t > hardwareBufferFrames, float volume)
bool isIdle(std::memory_order order=std::memory_order_relaxed) const
QPlatformAudioSourceStream(QAudioDevice, const QAudioFormat &, std::optional< int > ringbufferSize, std::optional< int32_t > hardwareBufferFrames, float volume)
#define __has_include(x)