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 QAUDIO_PLATFORM_IMPLEMENTATION_SUPPORT_P_H
5#define QAUDIO_PLATFORM_IMPLEMENTATION_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
20#include <QtMultimedia/private/qaudiosystem_p.h>
21#include <QtMultimedia/private/qaudiosystem_platform_stream_support_p.h>
22
23QT_BEGIN_NAMESPACE
24
25namespace QtMultimediaPrivate {
26
27///////////////////////////////////////////////////////////////////////////////////////////////////
28
29#ifdef __cpp_concepts
30// clang-format off
31template <typename T>
32concept QPlatformSinkStream = requires(T t, QIODevice *device,
33 QPlatformAudioIOStream::ShutdownPolicy shutdownPolicy,
34 AudioSinkCallback callback)
35{
36 requires std::constructible_from<
37 T,
38 QAudioDevice,
39 const QAudioFormat&,
40 std::optional<qsizetype>,
41 typename T::SinkType*,
42 float,
43 std::optional<int32_t>,
44 AudioEndpointRole
45 >;
46
47 { t.open() } -> std::same_as<bool>;
48
49 { t.start() } -> std::same_as<QIODevice *>;
50 { t.start(device) } -> std::same_as<bool>;
51 { t.start(std::move(callback)) } -> std::same_as<bool>;
52
53 { t.suspend() } -> std::same_as<void>;
54 { t.resume() } -> std::same_as<void>;
55 { t.stop(shutdownPolicy) } -> std::same_as<void>;
56
57 { t.setVolume(0.0f) } -> std::same_as<void>;
58 { t.bytesFree() } -> std::same_as<quint64>;
59
60 { t.processedDuration() } -> std::same_as<std::chrono::microseconds>;
61};
62
63// clang-format on
64# define STREAM_TYPE_ARG QPlatformSinkStream StreamType
65#else
66# define STREAM_TYPE_ARG typename StreamType
67#endif
68
69template <STREAM_TYPE_ARG, typename DerivedType = void>
70class QPlatformAudioSinkImplementation : public QPlatformAudioSink
71{
72public:
73 QPlatformAudioSinkImplementation(QAudioDevice device, const QAudioFormat &format,
74 QObject *parent);
75 ~QPlatformAudioSinkImplementation() override;
76
77 void start(QIODevice *device) override;
78 void start(AudioCallback &&) override;
79 QIODevice *start() override;
80
81 void stop() override final;
82 void reset() override;
83
84 void suspend() override;
85 void resume() override;
86
87 qsizetype bytesFree() const override;
88 void setBufferSize(qsizetype value) override;
89 qsizetype bufferSize() const override;
90 void setHardwareBufferFrames(int32_t) override;
91 int32_t hardwareBufferFrames() override;
92 qint64 processedUSecs() const override;
93
94 void setVolume(float volume) override;
95 bool hasCallbackAPI() override;
96 void setRole(AudioEndpointRole role) override;
97
98protected:
99 friend class QtMultimediaPrivate::QPlatformAudioSinkStream;
100 friend StreamType;
101 using ShutdownPolicy = QPlatformAudioIOStream::ShutdownPolicy;
102
103 std::optional<int> m_internalBufferSize;
104 std::optional<int32_t> m_hardwareBufferFrames;
105 AudioEndpointRole m_role = AudioEndpointRole::Other;
106
107 std::shared_ptr<StreamType> m_stream;
108
109 using ConcreteSinkType = std::conditional_t<std::is_same_v<DerivedType, void>,
110 QPlatformAudioSinkImplementation, DerivedType>;
111};
112
113template <STREAM_TYPE_ARG, typename DerivedType>
114QPlatformAudioSinkImplementation<StreamType, DerivedType>::QPlatformAudioSinkImplementation(
115 QAudioDevice device, const QAudioFormat &format, QObject *parent)
116 : QPlatformAudioSink(std::move(device), format, parent)
117{
118}
119
120template <STREAM_TYPE_ARG, typename DerivedType>
121QPlatformAudioSinkImplementation<StreamType, DerivedType>::~QPlatformAudioSinkImplementation()
122{
123 stop();
124}
125
126template <STREAM_TYPE_ARG, typename DerivedType>
127void QPlatformAudioSinkImplementation<StreamType, DerivedType>::start(QIODevice *device)
128{
129 if (!device) {
130 setError(QAudio::IOError);
131 return;
132 }
133
134 if (m_stream) {
135 qWarning(msg: "QAudioSink::start() called while already started");
136 return;
137 }
138
139 m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
140 static_cast<ConcreteSinkType *>(this), volume(),
141 m_hardwareBufferFrames, m_role);
142
143 if (!m_stream->open()) {
144 setError(QAudio::OpenError);
145 m_stream = {};
146 return;
147 }
148
149 m_stream->start(device);
150 updateStreamState(QAudio::ActiveState);
151}
152
153template <STREAM_TYPE_ARG, typename DerivedType>
154void QPlatformAudioSinkImplementation<StreamType, DerivedType>::start(AudioCallback &&audioCallback)
155{
156 using namespace QtMultimediaPrivate;
157 if (!validateAudioCallback(audioCallback, m_format)) {
158 setError(QAudio::OpenError);
159 return;
160 }
161
162 if (m_stream) {
163 qWarning(msg: "QAudioSink::start() called while already started");
164 return;
165 }
166
167 m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
168 static_cast<ConcreteSinkType *>(this), volume(),
169 m_hardwareBufferFrames, m_role);
170
171 if (!m_stream->open()) {
172 setError(QAudio::OpenError);
173 m_stream = {};
174 return;
175 }
176
177 bool started = m_stream->start(std::move(audioCallback));
178 if (!started) {
179 setError(QAudio::OpenError);
180 m_stream = {};
181 return;
182 }
183
184 updateStreamState(QAudio::ActiveState);
185}
186
187template <STREAM_TYPE_ARG, typename DerivedType>
188QIODevice *QPlatformAudioSinkImplementation<StreamType, DerivedType>::start()
189{
190 if (m_stream) {
191 qWarning(msg: "QAudioSink::start() called while already started");
192 return nullptr;
193 }
194
195 m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
196 static_cast<ConcreteSinkType *>(this), volume(),
197 m_hardwareBufferFrames, m_role);
198
199 if (!m_stream->open()) {
200 setError(QAudio::OpenError);
201 m_stream = {};
202 return nullptr;
203 }
204
205 QIODevice *device = m_stream->start();
206 QObject::connect(device, &QIODevice::readyRead, this, [this] {
207 updateStreamIdle(idle: false);
208 });
209 updateStreamIdle(idle: true);
210 updateStreamState(QAudio::ActiveState);
211 return device;
212}
213
214template <STREAM_TYPE_ARG, typename DerivedType>
215void QPlatformAudioSinkImplementation<StreamType, DerivedType>::stop()
216{
217 if (m_stream) {
218 m_stream->stop(ShutdownPolicy::DrainRingbuffer);
219 m_stream = {};
220 updateStreamState(QAudio::StoppedState);
221 }
222}
223
224template <STREAM_TYPE_ARG, typename DerivedType>
225void QPlatformAudioSinkImplementation<StreamType, DerivedType>::reset()
226{
227 if (m_stream) {
228 m_stream->stop(ShutdownPolicy::DiscardRingbuffer);
229 m_stream = {};
230 updateStreamState(QAudio::StoppedState);
231 }
232}
233
234template <STREAM_TYPE_ARG, typename DerivedType>
235void QPlatformAudioSinkImplementation<StreamType, DerivedType>::suspend()
236{
237 if (m_stream) {
238 m_stream->suspend();
239 updateStreamState(QAudio::SuspendedState);
240 }
241}
242
243template <STREAM_TYPE_ARG, typename DerivedType>
244void QPlatformAudioSinkImplementation<StreamType, DerivedType>::resume()
245{
246 if (m_stream) {
247 updateStreamState(QAudio::ActiveState);
248 m_stream->resume();
249 }
250}
251
252template <STREAM_TYPE_ARG, typename DerivedType>
253qsizetype QPlatformAudioSinkImplementation<StreamType, DerivedType>::bytesFree() const
254{
255 if (m_stream)
256 return m_stream->bytesFree();
257 return 0;
258}
259
260template <STREAM_TYPE_ARG, typename DerivedType>
261void QPlatformAudioSinkImplementation<StreamType, DerivedType>::setBufferSize(qsizetype value)
262{
263 m_internalBufferSize = value;
264}
265
266template <STREAM_TYPE_ARG, typename DerivedType>
267qsizetype QPlatformAudioSinkImplementation<StreamType, DerivedType>::bufferSize() const
268{
269 if (m_stream)
270 return m_stream->ringbufferSizeInBytes();
271
272 return QPlatformAudioIOStream::inferRingbufferBytes(ringbufferSize: m_internalBufferSize,
273 hardwareBufferFrames: m_hardwareBufferFrames, m_format);
274}
275
276template <STREAM_TYPE_ARG, typename DerivedType>
277void QPlatformAudioSinkImplementation<StreamType, DerivedType>::setHardwareBufferFrames(int32_t arg)
278{
279 if (arg > 0)
280 m_hardwareBufferFrames = arg;
281 else
282 m_hardwareBufferFrames = std::nullopt;
283}
284
285template <STREAM_TYPE_ARG, typename DerivedType>
286int32_t QPlatformAudioSinkImplementation<StreamType, DerivedType>::hardwareBufferFrames()
287{
288 return m_hardwareBufferFrames.value_or(u: -1);
289}
290
291template <STREAM_TYPE_ARG, typename DerivedType>
292qint64 QPlatformAudioSinkImplementation<StreamType, DerivedType>::processedUSecs() const
293{
294 if (m_stream)
295 return m_stream->processedDuration().count();
296
297 return 0;
298}
299
300template <STREAM_TYPE_ARG, typename DerivedType>
301void QPlatformAudioSinkImplementation<StreamType, DerivedType>::setVolume(float volume)
302{
303 QPlatformAudioEndpointBase::setVolume(volume);
304 if (m_stream)
305 m_stream->setVolume(volume);
306}
307
308template <STREAM_TYPE_ARG, typename DerivedType>
309bool QPlatformAudioSinkImplementation<StreamType, DerivedType>::hasCallbackAPI()
310{
311 return true;
312}
313
314template <STREAM_TYPE_ARG, typename DerivedType>
315void QPlatformAudioSinkImplementation<StreamType, DerivedType>::setRole(AudioEndpointRole role)
316{
317 m_role = role;
318}
319
320#undef STREAM_TYPE_ARG
321
322///////////////////////////////////////////////////////////////////////////////////////////////////
323
324#ifdef __cpp_concepts
325// clang-format off
326template <typename T>
327concept QPlatformSourceStream = requires(T t, QIODevice *device,
328 QPlatformAudioIOStream::ShutdownPolicy shutdownPolicy)
329{
330 requires std::constructible_from<
331 T,
332 QAudioDevice,
333 const QAudioFormat&,
334 std::optional<qsizetype>,
335 typename T::SourceType*,
336 float,
337 std::optional<int32_t>
338 >;
339
340 { t.open() } -> std::same_as<bool>;
341
342 { t.start() } -> std::same_as<QIODevice *>;
343 { t.start(device) } -> std::same_as<bool>;
344
345 { t.suspend() } -> std::same_as<void>;
346 { t.resume() } -> std::same_as<void>;
347 { t.stop(shutdownPolicy) } -> std::same_as<void>;
348
349 { t.setVolume(0.0f) } -> std::same_as<void>;
350 { t.bytesReady() } -> std::same_as<qsizetype>;
351 { t.deviceIsRingbufferReader() } -> std::same_as<bool>;
352
353 { t.processedDuration() } -> std::same_as<std::chrono::microseconds>;
354};
355
356// clang-format on
357# define STREAM_TYPE_ARG QPlatformSourceStream StreamType
358#else
359# define STREAM_TYPE_ARG typename StreamType
360#endif
361
362template <STREAM_TYPE_ARG, typename DerivedType = void>
363class QPlatformAudioSourceImplementation : public QPlatformAudioSource
364{
365public:
366 QPlatformAudioSourceImplementation(QAudioDevice device, const QAudioFormat &format,
367 QObject *parent);
368 ~QPlatformAudioSourceImplementation() override;
369
370 void start(QIODevice *device) override;
371 QIODevice *start() override;
372
373 void stop() override final;
374 void reset() override;
375
376 void suspend() override;
377 void resume() override;
378
379 qsizetype bytesReady() const override;
380 void setBufferSize(qsizetype value) override;
381 qsizetype bufferSize() const override;
382 void setHardwareBufferFrames(int32_t) override;
383 int32_t hardwareBufferFrames() override;
384 qint64 processedUSecs() const override;
385
386 void setVolume(float volume) override;
387
388protected:
389 friend class QtMultimediaPrivate::QPlatformAudioSourceStream;
390 friend StreamType;
391 using ShutdownPolicy = QPlatformAudioIOStream::ShutdownPolicy;
392
393 std::optional<int> m_internalBufferSize;
394 std::optional<int32_t> m_hardwareBufferFrames;
395
396 std::shared_ptr<StreamType> m_stream;
397 std::shared_ptr<StreamType> m_retiredStream;
398
399 using ConcreteSourceType = std::conditional_t<std::is_same_v<DerivedType, void>,
400 QPlatformAudioSourceImplementation, DerivedType>;
401};
402
403template <STREAM_TYPE_ARG, typename DerivedType>
404QPlatformAudioSourceImplementation<StreamType, DerivedType>::QPlatformAudioSourceImplementation(
405 QAudioDevice device, const QAudioFormat &format, QObject *parent)
406 : QPlatformAudioSource(std::move(device), format, parent)
407{
408}
409
410template <STREAM_TYPE_ARG, typename DerivedType>
411QPlatformAudioSourceImplementation<StreamType, DerivedType>::~QPlatformAudioSourceImplementation()
412{
413 stop();
414}
415
416template <STREAM_TYPE_ARG, typename DerivedType>
417void QPlatformAudioSourceImplementation<StreamType, DerivedType>::start(QIODevice *device)
418{
419 if (!device) {
420 setError(QAudio::IOError);
421 return;
422 }
423
424 if (m_stream) {
425 qWarning(msg: "QAudioSource::start() called while already started");
426 return;
427 }
428
429 m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
430 static_cast<ConcreteSourceType *>(this), volume(),
431 m_hardwareBufferFrames);
432
433 if (!m_stream->open()) {
434 setError(QAudio::OpenError);
435 m_stream = {};
436 return;
437 }
438
439 m_stream->start(device);
440 updateStreamState(QAudio::ActiveState);
441}
442
443template <STREAM_TYPE_ARG, typename DerivedType>
444QIODevice *QPlatformAudioSourceImplementation<StreamType, DerivedType>::start()
445{
446 if (m_stream) {
447 qWarning(msg: "QAudioSource::start() called while already started");
448 return nullptr;
449 }
450
451 m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
452 static_cast<ConcreteSourceType *>(this), volume(),
453 m_hardwareBufferFrames);
454
455 if (!m_stream->open()) {
456 setError(QAudio::OpenError);
457 m_stream = {};
458 return nullptr;
459 }
460
461 QIODevice *device = m_stream->start();
462 QObject::connect(device, &QIODevice::readyRead, this, [this] {
463 updateStreamIdle(idle: false);
464 });
465 updateStreamIdle(idle: true, EmitStateSignal::False);
466 updateStreamState(QAudio::ActiveState);
467 return device;
468}
469
470template <STREAM_TYPE_ARG, typename DerivedType>
471void QPlatformAudioSourceImplementation<StreamType, DerivedType>::stop()
472{
473 if (!m_stream)
474 return;
475
476 if (m_stream->deviceIsRingbufferReader())
477 // we own the qiodevice, so let's keep it alive to allow users to drain the ringbuffer
478 m_retiredStream = m_stream;
479
480 m_stream->stop(ShutdownPolicy::DrainRingbuffer);
481 m_stream = {};
482 updateStreamState(QAudio::StoppedState);
483}
484
485template <STREAM_TYPE_ARG, typename DerivedType>
486void QPlatformAudioSourceImplementation<StreamType, DerivedType>::reset()
487{
488 m_retiredStream = {};
489
490 if (!m_stream)
491 return;
492
493 m_stream->stop(ShutdownPolicy::DiscardRingbuffer);
494 m_stream = {};
495 updateStreamState(QAudio::StoppedState);
496}
497
498template <STREAM_TYPE_ARG, typename DerivedType>
499void QPlatformAudioSourceImplementation<StreamType, DerivedType>::suspend()
500{
501 if (m_stream) {
502 m_stream->suspend();
503 updateStreamState(QAudio::SuspendedState);
504 }
505}
506
507template <STREAM_TYPE_ARG, typename DerivedType>
508void QPlatformAudioSourceImplementation<StreamType, DerivedType>::resume()
509{
510 if (m_stream) {
511 updateStreamState(QAudio::ActiveState);
512 m_stream->resume();
513 }
514}
515
516template <STREAM_TYPE_ARG, typename DerivedType>
517qsizetype QPlatformAudioSourceImplementation<StreamType, DerivedType>::bytesReady() const
518{
519 return m_stream ? m_stream->bytesReady() : 0;
520}
521
522template <STREAM_TYPE_ARG, typename DerivedType>
523void QPlatformAudioSourceImplementation<StreamType, DerivedType>::setBufferSize(qsizetype value)
524{
525 m_internalBufferSize = value;
526}
527
528template <STREAM_TYPE_ARG, typename DerivedType>
529qsizetype QPlatformAudioSourceImplementation<StreamType, DerivedType>::bufferSize() const
530{
531 if (m_stream)
532 return m_stream->ringbufferSizeInBytes();
533
534 return QPlatformAudioIOStream::inferRingbufferBytes(ringbufferSize: m_internalBufferSize,
535 hardwareBufferFrames: m_hardwareBufferFrames, m_format);
536}
537
538template <STREAM_TYPE_ARG, typename DerivedType>
539void QPlatformAudioSourceImplementation<StreamType, DerivedType>::setHardwareBufferFrames(
540 int32_t arg)
541{
542 if (arg > 0)
543 m_hardwareBufferFrames = arg;
544 else
545 m_hardwareBufferFrames = std::nullopt;
546}
547
548template <STREAM_TYPE_ARG, typename DerivedType>
549int32_t QPlatformAudioSourceImplementation<StreamType, DerivedType>::hardwareBufferFrames()
550{
551 return m_hardwareBufferFrames.value_or(u: -1);
552}
553
554template <STREAM_TYPE_ARG, typename DerivedType>
555qint64 QPlatformAudioSourceImplementation<StreamType, DerivedType>::processedUSecs() const
556{
557 return m_stream ? m_stream->processedDuration().count() : 0;
558}
559
560template <STREAM_TYPE_ARG, typename DerivedType>
561void QPlatformAudioSourceImplementation<StreamType, DerivedType>::setVolume(float volume)
562{
563 QPlatformAudioEndpointBase::setVolume(volume);
564 if (m_stream)
565 m_stream->setVolume(volume);
566}
567
568///////////////////////////////////////////////////////////////////////////////////////////////////
569
570template <STREAM_TYPE_ARG, typename DerivedType = void>
571class QPlatformAudioSourceImplementationWithCallback
572 : public QPlatformAudioSourceImplementation<StreamType, DerivedType>
573{
574protected:
575 using BaseClass = QPlatformAudioSourceImplementation<StreamType, DerivedType>;
576 using BaseClass::BaseClass;
577 using BaseClass::start;
578
579 QT_WARNING_PUSH
580 QT_WARNING_DISABLE_CLANG("-Woverloaded-virtual")
581 void start(QPlatformAudioSource::AudioCallback &&) override;
582 QT_WARNING_POP
583 bool hasCallbackAPI() override { return true; }
584};
585
586template <STREAM_TYPE_ARG, typename DerivedType>
587void QPlatformAudioSourceImplementationWithCallback<StreamType, DerivedType>::start(
588 QPlatformAudioSource::AudioCallback &&audioCallback)
589{
590 using namespace QtMultimediaPrivate;
591 if (!validateAudioCallback(audioCallback, BaseClass::m_format)) {
592 BaseClass::setError(QAudio::OpenError);
593 return;
594 }
595
596 BaseClass::m_stream = std::make_shared<StreamType>(
597 BaseClass::m_audioDevice, BaseClass::m_format, BaseClass::m_internalBufferSize,
598 static_cast<typename BaseClass::ConcreteSourceType *>(this), BaseClass::volume(),
599 BaseClass::m_hardwareBufferFrames);
600
601 if (!BaseClass::m_stream->open()) {
602 BaseClass::setError(QAudio::OpenError);
603 BaseClass::m_stream = {};
604 return;
605 }
606
607 bool started = BaseClass::m_stream->start(std::move(audioCallback));
608 if (!started) {
609 BaseClass::setError(QAudio::OpenError);
610 BaseClass::m_stream = {};
611 return;
612 }
613
614 BaseClass::updateStreamState(QAudio::ActiveState);
615}
616
617#undef STREAM_TYPE_ARG
618
619} // namespace QtMultimediaPrivate
620
621QT_END_NAMESPACE
622
623#endif // QAUDIO_PLATFORM_IMPLEMENTATION_SUPPORT_P_H
624

source code of qtmultimedia/src/multimedia/audio/qaudio_platform_implementation_support_p.h