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->setError(QAudio::IOError);
174 parent->updateStreamState(QtAudio::State::StoppedState);
175
176 parent->m_stream = {};
177 }
178 }
179
180 QThread *thread() const;
181
182 template <typename Functor>
183 void invokeOnAppThread(Functor &&f)
184 {
185 // note: this is not a QObject, so we use the first QObject member of the stream as context
186 QMetaObject::invokeMethod(&m_streamIdleDetectionNotifier, std::forward<Functor>(f));
187 }
188
189private:
190 // qiodevice
191 QIODevice *m_device = nullptr;
192
193 // idle detection
194 std::atomic<bool> m_streamIsIdle = false;
195 QAutoResetEvent m_streamIdleDetectionNotifier;
196 QMetaObject::Connection m_streamIdleDetectionConnection;
197
198 // ringbuffer events
199 QAutoResetEvent m_ringbufferHasSpace;
200 QMetaObject::Connection m_ringbufferHasSpaceConnection;
201 QMetaObject::Connection m_iodeviceHasNewDataConnection;
202
203 std::unique_ptr<QtPrivate::QIODeviceRingBufferWriterBase> m_ringbufferWriterDevice;
204
205 // stats
206 std::atomic_int64_t m_totalFrameCount{};
207 std::atomic_int64_t m_processedFrameCount{};
208
209 void convertToNative(QSpan<const std::byte> internal, QSpan<std::byte> native, float volume,
210 NativeSampleFormat) noexcept QT_MM_NONBLOCKING;
211
212 // pullFromQIODeviceToRingbuffer is not reentrant. however we might end up in situations where a
213 // QIODevice emits readReady from within QIODevice::readData. We protect against this using a
214 // reentrancy guard and queue invocations if we detect a reentrant call
215 template <typename Functor>
216 void withPullIODeviceReentrancyGuard(Functor f)
217 {
218 if (!m_pullIODeviceReentrancyGuard) {
219 QScopedValueRollback<bool> guard{
220 m_pullIODeviceReentrancyGuard,
221 true,
222 };
223 f();
224 } else {
225 QMetaObject::invokeMethod(&m_streamIdleDetectionNotifier,
226 [this, f = std::move(f)]() mutable {
227 withPullIODeviceReentrancyGuard(std::move(f));
228 }, Qt::QueuedConnection);
229 }
230 }
231 bool m_pullIODeviceReentrancyGuard = false;
232
233 void pullFromQIODeviceImpl();
234};
235
236////////////////////////////////////////////////////////////////////////////////////////////////////
237
239{
240public:
241 using QPlatformAudioIOStream::ShutdownPolicy;
243
244 using QPlatformAudioIOStream::requestStop;
245
246protected:
247 QPlatformAudioSourceStream(QAudioDevice, const QAudioFormat &,
248 std::optional<int> ringbufferSize,
249 std::optional<int32_t> hardwareBufferFrames, float volume);
251
253
256
257 // ringbuffer / stats
258 qsizetype bytesReady() const;
260
261 // iodevice
262 void setQIODevice(QIODevice *device);
263 void createQIODeviceConnections(QIODevice *device);
266 void pushToIODevice();
267 bool deviceIsRingbufferReader() const;
269 void emptyRingbuffer();
270
271 // downstream delegates
272 virtual void updateStreamIdle(bool) = 0;
273
274 template <typename ParentType>
275 void handleIOError(ParentType *parent)
276 {
277 if (parent) {
278 Q_ASSERT(thread()->isCurrentThread());
279 parent->setError(QAudio::IOError);
280 parent->updateStreamState(QtAudio::State::StoppedState);
281
283 // we own the qiodevice, so let's keep it alive to allow users to drain the
284 // ringbuffer
285 parent->m_retiredStream = std::move(parent->m_stream);
286 else
287 parent->m_stream = {};
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)
#define __has_include(x)