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
126 using QPlatformAudioIOStream::requestStop;
127
128protected:
129 QPlatformAudioSinkStream(QAudioDevice, const QAudioFormat &, std::optional<int> ringbufferSize,
130 std::optional<int32_t> hardwareBufferFrames, float volume);
133
136
137 // ringbuffer / stats
138 quint64 bytesFree() const;
140
141 // downstream delegates
142 virtual void updateStreamIdle(bool) = 0;
143
144 // iodevice
146 void setQIODevice(QIODevice *device);
147 void createQIODeviceConnections(QIODevice *device);
149 void pullFromQIODevice();
150
151 // LATER: do we want to relax notifying the app thread?
152 static constexpr int notificationThresholdBytes = 0;
153
154 // idle detection
155 void setIdleState(bool);
156 bool isIdle(std::memory_order order = std::memory_order_relaxed) const
157 {
158 return m_streamIsIdle.load(order);
159 }
160 void stopIdleDetection();
161
162 template <typename Functor>
163 auto connectIdleHandler(Functor &&f)
164 {
165 return m_streamIdleDetectionNotifier.callOnActivated(std::forward<Functor>(f));
166 }
167
168 template <typename ParentType>
169 void handleIOError(ParentType *parent)
170 {
171 if (parent) {
172 Q_ASSERT(thread()->isCurrentThread());
173 parent->m_stream = {};
174 parent->setError(QAudio::IOError);
175 parent->updateStreamState(QtAudio::State::StoppedState);
176 }
177 }
178
179 QThread *thread() const;
180
181 template <typename Functor>
182 void invokeOnAppThread(Functor &&f)
183 {
184 // note: this is not a QObject, so we use the first QObject member of the stream as context
185 QMetaObject::invokeMethod(&m_streamIdleDetectionNotifier, std::forward<Functor>(f));
186 }
187
188private:
189 // qiodevice
190 QIODevice *m_device = nullptr;
191
192 // idle detection
193 std::atomic<bool> m_streamIsIdle = false;
194 QAutoResetEvent m_streamIdleDetectionNotifier;
195 QMetaObject::Connection m_streamIdleDetectionConnection;
196
197 // ringbuffer events
198 QAutoResetEvent m_ringbufferHasSpace;
199 QMetaObject::Connection m_ringbufferHasSpaceConnection;
200 QMetaObject::Connection m_iodeviceHasNewDataConnection;
201
202 std::unique_ptr<QtPrivate::QIODeviceRingBufferWriterBase> m_ringbufferWriterDevice;
203
204 // stats
205 std::atomic_int64_t m_totalFrameCount{};
206 std::atomic_int64_t m_processedFrameCount{};
207
208 void convertToNative(QSpan<const std::byte> internal, QSpan<std::byte> native, float volume,
209 NativeSampleFormat) noexcept QT_MM_NONBLOCKING;
210
211 // pullFromQIODeviceToRingbuffer is not reentrant. however we might end up in situations where a
212 // QIODevice emits readReady from within QIODevice::readData. We protect against this using a
213 // reentrancy guard and queue invocations if we detect a reentrant call
214 template <typename Functor>
215 void withPullIODeviceReentrancyGuard(Functor f)
216 {
217 if (!m_pullIODeviceReentrancyGuard) {
218 QScopedValueRollback<bool> guard{
219 m_pullIODeviceReentrancyGuard,
220 true,
221 };
222 f();
223 } else {
224 QMetaObject::invokeMethod(&m_streamIdleDetectionNotifier,
225 [this, f = std::move(f)]() mutable {
226 withPullIODeviceReentrancyGuard(std::move(f));
227 }, Qt::QueuedConnection);
228 }
229 }
230 bool m_pullIODeviceReentrancyGuard = false;
231
232 void pullFromQIODeviceImpl();
233};
234
235////////////////////////////////////////////////////////////////////////////////////////////////////
236
238{
239public:
240 using QPlatformAudioIOStream::ShutdownPolicy;
242
243 using QPlatformAudioIOStream::requestStop;
244
245protected:
246 QPlatformAudioSourceStream(QAudioDevice, const QAudioFormat &,
247 std::optional<int> ringbufferSize,
248 std::optional<int32_t> hardwareBufferFrames, float volume);
250
252
255
256 // ringbuffer / stats
257 qsizetype bytesReady() const;
259
260 // iodevice
261 void setQIODevice(QIODevice *device);
262 void createQIODeviceConnections(QIODevice *device);
265 void pushToIODevice();
266 bool deviceIsRingbufferReader() const;
268 void emptyRingbuffer();
269
270 // downstream delegates
271 virtual void updateStreamIdle(bool) = 0;
272
273 template <typename ParentType>
274 void handleIOError(ParentType *parent)
275 {
276 if (parent) {
277 Q_ASSERT(thread()->isCurrentThread());
278
280 // we own the qiodevice, so let's keep it alive to allow users to drain the
281 // ringbuffer
282 parent->m_retiredStream = std::move(parent->m_stream);
283 else
284 parent->m_stream = {};
285
286 parent->setError(QAudio::IOError);
287 parent->updateStreamState(QtAudio::State::StoppedState);
288 }
289 }
290
291 QThread *thread() const;
292
293 template <typename Functor>
294 void invokeOnAppThread(Functor &&f)
295 {
296 // note: this is not a QObject, so we use the first QObject member of the stream as context
297 QMetaObject::invokeMethod(&m_ringbufferHasData, std::forward<Functor>(f));
298 }
299
300private:
301 // qiodevice
302 QIODevice *m_device = nullptr;
303 std::unique_ptr<QIODevice> m_ringbufferReaderDevice;
304
305 // ringbuffer events
306 QAutoResetEvent m_ringbufferHasData;
307 QAutoResetEvent m_ringbufferIsFull;
308
309 QMetaObject::Connection m_ringbufferHasDataConnection;
310 QMetaObject::Connection m_ringbufferIsFullConnection;
311
312 // stats
313 std::atomic_uint64_t m_totalNumberOfFramesPushedToRingbuffer{};
314
315 void convertFromNative(QSpan<const std::byte> native, QSpan<std::byte> internal, float volume,
316 NativeSampleFormat) noexcept QT_MM_NONBLOCKING;
317};
318
319} // namespace QtMultimediaPrivate
320
321QT_END_NAMESPACE
322
323#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)
Combined button and popup list for selecting options.