Revision control

++ // Copyright (C) 2025 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++
++ #ifndef QAUDIO_PLATFORM_IMPLEMENTATION_SUPPORT_P_H
++ #define QAUDIO_PLATFORM_IMPLEMENTATION_SUPPORT_P_H
++
++ //
++ // W A R N I N G
++ // -------------
++ //
++ // This file is not part of the Qt API. It exists purely as an
++ // implementation detail. This header file may change from version to
++ // version without notice, or even be removed.
++ //
++ // We mean it.
++ //
++
++ #include <QtMultimedia/private/qaudiosystem_p.h>
++ #include <QtMultimedia/private/qaudiosystem_platform_stream_support_p.h>
++ #include <QtMultimedia/qtmultimediaglobal.h>
++
++ QT_BEGIN_NAMESPACE
++
++ namespace QtMultimediaPrivate {
++
++ ///////////////////////////////////////////////////////////////////////////////////////////////////
++
++ #ifdef __cpp_concepts
++ // clang-format off
++ template <typename T>
++ concept QPlatformSinkStream = requires(T t, QIODevice *device,
++ QPlatformAudioIOStream::ShutdownPolicy shutdownPolicy,
++ AudioSinkCallback callback)
++ {
++ requires std::constructible_from<
++ T,
++ QAudioDevice,
++ const QAudioFormat&,
++ std::optional<qsizetype>,
++ typename T::SinkType*,
++ float,
++ std::optional<NativePeriodFrames>,
++ AudioEndpointRole
++ >;
++
++ { t.open() } -> std::same_as<bool>;
++
++ { t.start() } -> std::same_as<QIODevice *>;
++ { t.start(device) } -> std::same_as<bool>;
++ { t.start(std::move(callback)) } -> std::same_as<bool>;
++
++ { t.suspend() } -> std::same_as<void>;
++ { t.resume() };
++ { t.stop(shutdownPolicy) } -> std::same_as<void>;
++
++ { t.setVolume(0.0f) } -> std::same_as<void>;
++ { t.bytesFree() } -> std::same_as<quint64>;
++
++ { t.processedDuration() } -> std::same_as<std::chrono::microseconds>;
++ };
++
++ // clang-format on
++ # define STREAM_TYPE_ARG QPlatformSinkStream StreamType
++ #else
++ # define STREAM_TYPE_ARG typename StreamType
++ #endif
++
++ template <STREAM_TYPE_ARG, typename DerivedType = void>
++ class QPlatformAudioSinkImplementation : public QPlatformAudioSink
++ {
++ public:
++ QPlatformAudioSinkImplementation(QAudioDevice device, const QAudioFormat &format,
++ QObject *parent);
++ ~QPlatformAudioSinkImplementation() override;
++
++ void start(QIODevice *device) override;
++ void start(AudioCallback &&) override;
++ QIODevice *start() override;
++
++ void stop() override final;
++ void reset() override;
++
++ void suspend() override;
++ void resume() override;
++
++ qsizetype bytesFree() const override;
++ void setBufferSize(qsizetype value) override;
++ qsizetype bufferSize() const override;
++ void setNativePeriodFrames(std::optional<NativePeriodFrames>) override;
++ std::optional<NativePeriodFrames> nativePeriodFrames() override;
++ qint64 processedUSecs() const override;
++
++ void setVolume(float volume) override;
++ bool hasCallbackAPI() override;
++ void setRole(AudioEndpointRole role) override;
++
++ protected:
++ void handleStreamOpenError();
++
++ friend class QtMultimediaPrivate::QPlatformAudioSinkStream;
++ friend StreamType;
++ using ShutdownPolicy = QPlatformAudioIOStream::ShutdownPolicy;
++
++ std::optional<int> m_internalBufferSize;
++ std::optional<NativePeriodFrames> m_nativePeriodFrames;
++ AudioEndpointRole m_role = AudioEndpointRole::Other;
++
++ std::shared_ptr<StreamType> m_stream;
++
++ using ConcreteSinkType = std::conditional_t<std::is_same_v<DerivedType, void>,
++ QPlatformAudioSinkImplementation, DerivedType>;
++ };
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ QPlatformAudioSinkImplementation<StreamType, DerivedType>::QPlatformAudioSinkImplementation(
++ QAudioDevice device, const QAudioFormat &format, QObject *parent)
++ : QPlatformAudioSink(std::move(device), format, parent)
++ {
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ QPlatformAudioSinkImplementation<StreamType, DerivedType>::~QPlatformAudioSinkImplementation()
++ {
++ stop();
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::start(QIODevice *device)
++ {
++ if (!device) {
++ setError(QAudio::IOError);
++ return;
++ }
++
++ if (m_stream) {
++ qWarning("QAudioSink::start() called while already started");
++ return;
++ }
++
++ m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
++ static_cast<ConcreteSinkType *>(this), volume(),
++ m_nativePeriodFrames, m_role);
++
++ if (!m_stream->open())
++ return handleStreamOpenError();
++
++ bool started = m_stream->start(device);
++ if (!started)
++ return handleStreamOpenError();
++
++ updateStreamState(QAudio::ActiveState);
++ }
++
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::handleStreamOpenError()
++ {
++ m_stream->requestStop();
++ setError(QAudio::OpenError);
++ m_stream = {};
++ }
++
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::start(AudioCallback &&audioCallback)
++ {
++ using namespace QtMultimediaPrivate;
++ if (m_stream) {
++ qWarning("QAudioSink::start() called while already started");
++ return;
++ }
++
++ m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
++ static_cast<ConcreteSinkType *>(this), volume(),
++ m_nativePeriodFrames, m_role);
++
++ if (!m_stream->open())
++ return handleStreamOpenError();
++
++ bool started = m_stream->start(std::move(audioCallback));
++ if (!started)
++ return handleStreamOpenError();
++
++ updateStreamState(QAudio::ActiveState);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ QIODevice *QPlatformAudioSinkImplementation<StreamType, DerivedType>::start()
++ {
++ if (m_stream) {
++ qWarning("QAudioSink::start() called while already started");
++ return nullptr;
++ }
++
++ m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
++ static_cast<ConcreteSinkType *>(this), volume(),
++ m_nativePeriodFrames, m_role);
++
++ if (!m_stream->open()) {
++ handleStreamOpenError();
++ return nullptr;
++ }
++
++ QIODevice *device = m_stream->start();
++ if (!device) {
++ handleStreamOpenError();
++ return nullptr;
++ }
++
++ QObject::connect(device, &QIODevice::readyRead, this, [this] {
++ updateStreamIdle(false);
++ });
++ updateStreamIdle(true);
++ updateStreamState(QAudio::ActiveState);
++ return device;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::stop()
++ {
++ if (m_stream) {
++ m_stream->stop(ShutdownPolicy::DrainRingbuffer);
++ m_stream = {};
++ updateStreamState(QAudio::StoppedState);
++ }
++ setError(QtAudio::NoError);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::reset()
++ {
++ if (m_stream) {
++ m_stream->stop(ShutdownPolicy::DiscardRingbuffer);
++ m_stream = {};
++ updateStreamState(QAudio::StoppedState);
++ }
++ setError(QtAudio::NoError);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::suspend()
++ {
++ if (m_stream) {
++ if (state() == QAudio::SuspendedState)
++ return;
++
++ m_stream->suspend();
++ updateStreamState(QAudio::SuspendedState);
++ }
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::resume()
++ {
++ if (m_stream) {
++ if (state() != QAudio::SuspendedState)
++ return;
++
++ updateStreamState(QAudio::ActiveState);
++ m_stream->resume();
++ }
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ qsizetype QPlatformAudioSinkImplementation<StreamType, DerivedType>::bytesFree() const
++ {
++ if (m_stream)
++ return m_stream->bytesFree();
++ return 0;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::setBufferSize(qsizetype value)
++ {
++ m_internalBufferSize = value;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ qsizetype QPlatformAudioSinkImplementation<StreamType, DerivedType>::bufferSize() const
++ {
++ if (m_stream)
++ return m_stream->ringbufferSizeInBytes();
++
++ return QPlatformAudioIOStream::inferRingbufferBytes(m_internalBufferSize,
++ m_nativePeriodFrames, m_format);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::setNativePeriodFrames(
++ std::optional<NativePeriodFrames> arg)
++ {
++ m_nativePeriodFrames = arg;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ std::optional<NativePeriodFrames>
++ QPlatformAudioSinkImplementation<StreamType, DerivedType>::nativePeriodFrames()
++ {
++ return m_nativePeriodFrames;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ qint64 QPlatformAudioSinkImplementation<StreamType, DerivedType>::processedUSecs() const
++ {
++ if (m_stream)
++ return m_stream->processedDuration().count();
++
++ return 0;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::setVolume(float volume)
++ {
++ QPlatformAudioEndpointBase::setVolume(volume);
++ if (m_stream)
++ m_stream->setVolume(volume);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ bool QPlatformAudioSinkImplementation<StreamType, DerivedType>::hasCallbackAPI()
++ {
++ return true;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSinkImplementation<StreamType, DerivedType>::setRole(AudioEndpointRole role)
++ {
++ m_role = role;
++ }
++
++ #undef STREAM_TYPE_ARG
++
++ ///////////////////////////////////////////////////////////////////////////////////////////////////
++
++ #ifdef __cpp_concepts
++ // clang-format off
++ template <typename T>
++ concept QPlatformSourceStream = requires(T t, QIODevice *device,
++ QPlatformAudioIOStream::ShutdownPolicy shutdownPolicy)
++ {
++ requires std::constructible_from<
++ T,
++ QAudioDevice,
++ const QAudioFormat&,
++ std::optional<qsizetype>,
++ typename T::SourceType*,
++ float,
++ std::optional<NativePeriodFrames>
++ >;
++
++ { t.open() } -> std::same_as<bool>;
++
++ { t.start() } -> std::same_as<QIODevice *>;
++ { t.start(device) } -> std::same_as<bool>;
++
++ { t.suspend() } -> std::same_as<void>;
++ { t.resume() };
++ { t.stop(shutdownPolicy) } -> std::same_as<void>;
++
++ { t.setVolume(0.0f) } -> std::same_as<void>;
++ { t.bytesReady() } -> std::same_as<qsizetype>;
++ { t.deviceIsRingbufferReader() } -> std::same_as<bool>;
++
++ { t.processedDuration() } -> std::same_as<std::chrono::microseconds>;
++ };
++
++ // clang-format on
++ # define STREAM_TYPE_ARG QPlatformSourceStream StreamType
++ #else
++ # define STREAM_TYPE_ARG typename StreamType
++ #endif
++
++ template <STREAM_TYPE_ARG, typename DerivedType = void>
++ class QPlatformAudioSourceImplementation : public QPlatformAudioSource
++ {
++ public:
++ QPlatformAudioSourceImplementation(QAudioDevice device, const QAudioFormat &format,
++ QObject *parent);
++ ~QPlatformAudioSourceImplementation() override;
++
++ void start(QIODevice *device) override;
++ QIODevice *start() override;
++
++ void stop() override final;
++ void reset() override;
++
++ void suspend() override;
++ void resume() override;
++
++ qsizetype bytesReady() const override;
++ void setBufferSize(qsizetype value) override;
++ qsizetype bufferSize() const override;
++ void setNativePeriodFrames(std::optional<NativePeriodFrames>) override;
++ std::optional<NativePeriodFrames> nativePeriodFrames() override;
++ qint64 processedUSecs() const override;
++
++ void setVolume(float volume) override;
++
++ protected:
++ void handleStreamOpenError();
++
++ friend class QtMultimediaPrivate::QPlatformAudioSourceStream;
++ friend StreamType;
++ using ShutdownPolicy = QPlatformAudioIOStream::ShutdownPolicy;
++
++ std::optional<int> m_internalBufferSize;
++ std::optional<NativePeriodFrames> m_nativePeriodFrames;
++
++ std::shared_ptr<StreamType> m_stream;
++ std::shared_ptr<StreamType> m_retiredStream;
++
++ using ConcreteSourceType = std::conditional_t<std::is_same_v<DerivedType, void>,
++ QPlatformAudioSourceImplementation, DerivedType>;
++ };
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ QPlatformAudioSourceImplementation<StreamType, DerivedType>::QPlatformAudioSourceImplementation(
++ QAudioDevice device, const QAudioFormat &format, QObject *parent)
++ : QPlatformAudioSource(std::move(device), format, parent)
++ {
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ QPlatformAudioSourceImplementation<StreamType, DerivedType>::~QPlatformAudioSourceImplementation()
++ {
++ stop();
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::handleStreamOpenError()
++ {
++ m_stream->requestStop();
++ setError(QAudio::OpenError);
++ m_stream = {};
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::start(QIODevice *device)
++ {
++ if (!device) {
++ setError(QAudio::IOError);
++ return;
++ }
++
++ if (m_stream) {
++ qWarning("QAudioSource::start() called while already started");
++ return;
++ }
++
++ m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
++ static_cast<ConcreteSourceType *>(this), volume(),
++ m_nativePeriodFrames);
++
++ if (!m_stream->open())
++ return handleStreamOpenError();
++
++ bool started = m_stream->start(device);
++ if (!started)
++ return handleStreamOpenError();
++
++ updateStreamState(QAudio::ActiveState);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ QIODevice *QPlatformAudioSourceImplementation<StreamType, DerivedType>::start()
++ {
++ if (m_stream) {
++ qWarning("QAudioSource::start() called while already started");
++ return nullptr;
++ }
++
++ m_stream = std::make_shared<StreamType>(m_audioDevice, m_format, m_internalBufferSize,
++ static_cast<ConcreteSourceType *>(this), volume(),
++ m_nativePeriodFrames);
++
++ if (!m_stream->open()) {
++ handleStreamOpenError();
++ return nullptr;
++ }
++
++ QIODevice *device = m_stream->start();
++ if (!device) {
++ handleStreamOpenError();
++ return nullptr;
++ }
++
++ QObject::connect(device, &QIODevice::readyRead, this, [this] {
++ updateStreamIdle(false);
++ });
++ updateStreamIdle(true, EmitStateSignal::False);
++ updateStreamState(QAudio::ActiveState);
++ return device;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::stop()
++ {
++ if (!m_stream)
++ return;
++
++ if (m_stream->deviceIsRingbufferReader())
++ // we own the qiodevice, so let's keep it alive to allow users to drain the ringbuffer
++ m_retiredStream = m_stream;
++
++ m_stream->stop(ShutdownPolicy::DrainRingbuffer);
++ m_stream = {};
++ setError(QtAudio::NoError);
++ updateStreamState(QAudio::StoppedState);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::reset()
++ {
++ m_retiredStream = {};
++
++ if (!m_stream)
++ return;
++
++ m_stream->stop(ShutdownPolicy::DiscardRingbuffer);
++ m_stream = {};
++ setError(QtAudio::NoError);
++ updateStreamState(QAudio::StoppedState);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::suspend()
++ {
++ if (m_stream) {
++ m_stream->suspend();
++ updateStreamState(QAudio::SuspendedState);
++ }
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::resume()
++ {
++ if (m_stream) {
++ updateStreamState(QAudio::ActiveState);
++ m_stream->resume();
++ }
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ qsizetype QPlatformAudioSourceImplementation<StreamType, DerivedType>::bytesReady() const
++ {
++ return m_stream ? m_stream->bytesReady() : 0;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::setBufferSize(qsizetype value)
++ {
++ m_internalBufferSize = value;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ qsizetype QPlatformAudioSourceImplementation<StreamType, DerivedType>::bufferSize() const
++ {
++ if (m_stream)
++ return m_stream->ringbufferSizeInBytes();
++
++ return QPlatformAudioIOStream::inferRingbufferBytes(m_internalBufferSize,
++ m_nativePeriodFrames, m_format);
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::setNativePeriodFrames(
++ std::optional<NativePeriodFrames> arg)
++ {
++ m_nativePeriodFrames = arg;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ std::optional<NativePeriodFrames>
++ QPlatformAudioSourceImplementation<StreamType, DerivedType>::nativePeriodFrames()
++ {
++ return m_nativePeriodFrames;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ qint64 QPlatformAudioSourceImplementation<StreamType, DerivedType>::processedUSecs() const
++ {
++ return m_stream ? m_stream->processedDuration().count() : 0;
++ }
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementation<StreamType, DerivedType>::setVolume(float volume)
++ {
++ QPlatformAudioEndpointBase::setVolume(volume);
++ if (m_stream)
++ m_stream->setVolume(volume);
++ }
++
++ ///////////////////////////////////////////////////////////////////////////////////////////////////
++
++ template <STREAM_TYPE_ARG, typename DerivedType = void>
++ class QPlatformAudioSourceImplementationWithCallback
++ : public QPlatformAudioSourceImplementation<StreamType, DerivedType>
++ {
++ protected:
++ using BaseClass = QPlatformAudioSourceImplementation<StreamType, DerivedType>;
++ using BaseClass::BaseClass;
++ using BaseClass::start;
++
++ QT_WARNING_PUSH
++ QT_WARNING_DISABLE_CLANG("-Woverloaded-virtual")
++ void start(QPlatformAudioSource::AudioCallback &&) override;
++ QT_WARNING_POP
++ bool hasCallbackAPI() override { return true; }
++ };
++
++ template <STREAM_TYPE_ARG, typename DerivedType>
++ void QPlatformAudioSourceImplementationWithCallback<StreamType, DerivedType>::start(
++ QPlatformAudioSource::AudioCallback &&audioCallback)
++ {
++ using namespace QtMultimediaPrivate;
++
++ BaseClass::m_stream = std::make_shared<StreamType>(
++ BaseClass::m_audioDevice, BaseClass::m_format, BaseClass::m_internalBufferSize,
++ static_cast<typename BaseClass::ConcreteSourceType *>(this), BaseClass::volume(),
++ BaseClass::m_nativePeriodFrames);
++
++ if (!BaseClass::m_stream->open())
++ return BaseClass::handleStreamOpenError();
++
++ bool started = BaseClass::m_stream->start(std::move(audioCallback));
++ if (!started)
++ return BaseClass::handleStreamOpenError();
++
++ BaseClass::updateStreamState(QAudio::ActiveState);
++ }
++
++ #undef STREAM_TYPE_ARG
++
++ } // namespace QtMultimediaPrivate
++
++ QT_END_NAMESPACE
++
++ #endif // QAUDIO_PLATFORM_IMPLEMENTATION_SUPPORT_P_H