diff options
Diffstat (limited to 'src/multimedia/audio/qaudiostatemachine_p.h')
-rw-r--r-- | src/multimedia/audio/qaudiostatemachine_p.h | 167 |
1 files changed, 167 insertions, 0 deletions
diff --git a/src/multimedia/audio/qaudiostatemachine_p.h b/src/multimedia/audio/qaudiostatemachine_p.h new file mode 100644 index 000000000..44bef3ab0 --- /dev/null +++ b/src/multimedia/audio/qaudiostatemachine_p.h @@ -0,0 +1,167 @@ +// Copyright (C) 2023 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 QAUDIOSTATEMACHINE_P_H +#define QAUDIOSTATEMACHINE_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/qaudio.h> + +#include <qmutex.h> +#include <qwaitcondition.h> +#include <qpointer.h> +#include <atomic> +#include <chrono> + +QT_BEGIN_NAMESPACE + +class QAudioStateChangeNotifier; + +/* QAudioStateMachine provides an opportunity to + * toggle QAudio::State with QAudio::Error in + * a thread-safe manner. + * The toggling functions return a guard, + * which scope guaranties thread safety (if synchronization enabled) + * and notifies about the change via + * QAudioStateChangeNotifier::stateChanged and errorChanged. + * + * The state machine is supposed to be used mostly in + * QAudioSink and QAudioSource implementations. + */ +class Q_MULTIMEDIA_EXPORT QAudioStateMachine +{ +public: + using RawState = int; + class StateChangeGuard + { + public: + void reset() + { + if (auto stateMachine = std::exchange(m_stateMachine, nullptr)) + stateMachine->reset(m_state, m_prevState, m_error); + } + + ~StateChangeGuard() { reset(); } + + StateChangeGuard(const StateChangeGuard &) = delete; + StateChangeGuard(StateChangeGuard &&other) + : m_stateMachine(std::exchange(other.m_stateMachine, nullptr)), + m_state(other.m_state), + m_prevState(other.m_prevState), + m_error(other.m_error) + { + } + + operator bool() const { return m_stateMachine != nullptr; } + + void setError(QAudio::Error error) { m_error = error; } + + // Can be added make state changing more flexible + // but needs some investigation to ensure state change consistency + // The method is supposed to be used for sync read/write + // under "guard = updateActiveOrIdle(isActive)" + // void setState(QAudio::State state) { ... } + + bool isStateChanged() const { return m_state != m_prevState; } + + QAudio::State prevState() const { return QAudio::State(m_prevState); } + + private: + StateChangeGuard(QAudioStateMachine *stateMachine = nullptr, + RawState state = QAudio::StoppedState, + RawState prevState = QAudio::StoppedState, + QAudio::Error error = QAudio::NoError) + : m_stateMachine(stateMachine), m_state(state), m_prevState(prevState), m_error(error) + { + } + + private: + QAudioStateMachine *m_stateMachine; + RawState m_state; + const RawState m_prevState; + QAudio::Error m_error; + + friend class QAudioStateMachine; + }; + + QAudioStateMachine(QAudioStateChangeNotifier ¬ifier, bool synchronize = true); + + ~QAudioStateMachine(); + + QAudio::State state() const; + + QAudio::Error error() const; + + bool isDraining() const; + + bool isActiveOrIdle() const; + + // atomicaly checks if the state is stopped and marked as drained + std::pair<bool, bool> getDrainedAndStopped() const; + + // waits if the method stop(error, true) has bee called + void waitForDrained(std::chrono::milliseconds timeout); + + // mark as drained and wake up the method waitForDrained + void onDrained(); + + // Active/Idle/Suspended -> Stopped + StateChangeGuard stop(QAudio::Error error = QAudio::NoError, bool shouldDrain = false, + bool forceUpdateError = false); + + // Active/Idle/Suspended -> Stopped + StateChangeGuard stopOrUpdateError(QAudio::Error error = QAudio::NoError) + { + return stop(error, false, true); + } + + // Stopped -> Active/Idle + StateChangeGuard start(bool isActive = true); + + // Active/Idle -> Suspended + saves the exchanged state + StateChangeGuard suspend(); + + // Suspended -> saved state (Active/Idle) + StateChangeGuard resume(); + + // Idle -> Active + StateChangeGuard activateFromIdle(); + + // Active/Idle -> Active/Idle + updateError + StateChangeGuard updateActiveOrIdle(bool isActive, QAudio::Error error = QAudio::NoError); + + // Any -> Any; better use more strict methods + StateChangeGuard forceSetState(QAudio::State state, QAudio::Error error = QAudio::NoError); + + // force set the error + void setError(QAudio::Error error); + +private: + StateChangeGuard changeState(std::pair<RawState, uint32_t> prevStatesSet, RawState state, + QAudio::Error error = QAudio::NoError, bool shouldDrain = false); + + void reset(RawState state, RawState prevState, QAudio::Error error); + +private: + QPointer<QAudioStateChangeNotifier> m_notifier; + std::atomic<RawState> m_state = QAudio::StoppedState; + std::atomic<QAudio::Error> m_error = QAudio::NoError; + RawState m_suspendedInState = QAudio::SuspendedState; + + struct Synchronizer; + std::unique_ptr<Synchronizer> m_sychronizer; +}; + +QT_END_NAMESPACE + +#endif // QAUDIOSTATEMACHINE_P_H |