1// Copyright (C) 2023 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 QAUDIOSTATEMACHINE_P_H
5#define QAUDIOSTATEMACHINE_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/qaudio.h>
19
20#include <qmutex.h>
21#include <qwaitcondition.h>
22#include <qpointer.h>
23#include <atomic>
24#include <chrono>
25
26QT_BEGIN_NAMESPACE
27
28class QAudioStateChangeNotifier;
29
30/* QAudioStateMachine provides an opportunity to
31 * toggle QAudio::State with QAudio::Error in
32 * a thread-safe manner.
33 * The toggling functions return a notifier,
34 * which notifies about the change via
35 * QAudioStateChangeNotifier::stateChanged and errorChanged.
36 *
37 * The state machine is supposed to be used mostly in
38 * QAudioSink and QAudioSource implementations.
39 */
40class Q_MULTIMEDIA_EXPORT QAudioStateMachine
41{
42public:
43 using RawState = int;
44 class Notifier
45 {
46 public:
47 void reset()
48 {
49 if (auto stateMachine = std::exchange(obj&: m_stateMachine, new_val: nullptr))
50 stateMachine->reset(state: m_state, prevState: m_prevState, error: m_error);
51 }
52
53 ~Notifier() { reset(); }
54
55 Notifier(const Notifier &) = delete;
56 Notifier(Notifier &&other) noexcept
57 : m_stateMachine(std::exchange(obj&: other.m_stateMachine, new_val: nullptr)),
58 m_state(other.m_state),
59 m_prevState(other.m_prevState),
60 m_error(other.m_error)
61 {
62 }
63
64 operator bool() const { return m_stateMachine != nullptr; }
65
66 void setError(QAudio::Error error) { m_error = error; }
67
68 // Can be added make state changing more flexible
69 // but needs some investigation to ensure state change consistency
70 // The method is supposed to be used for sync read/write
71 // under "notifier = updateActiveOrIdle(isActive)"
72 // void setState(QAudio::State state) { ... }
73
74 bool isStateChanged() const { return m_state != m_prevState; }
75
76 QAudio::State prevState() const { return QAudio::State(m_prevState); }
77
78 private:
79 Notifier(QAudioStateMachine *stateMachine = nullptr, RawState state = QAudio::StoppedState,
80 RawState prevState = QAudio::StoppedState, QAudio::Error error = QAudio::NoError)
81 : m_stateMachine(stateMachine), m_state(state), m_prevState(prevState), m_error(error)
82 {
83 }
84
85 private:
86 QAudioStateMachine *m_stateMachine;
87 RawState m_state;
88 const RawState m_prevState;
89 QAudio::Error m_error;
90
91 friend class QAudioStateMachine;
92 };
93
94 QAudioStateMachine(QAudioStateChangeNotifier &notifier, bool synchronize = true);
95
96 ~QAudioStateMachine();
97
98 QAudio::State state() const;
99
100 QAudio::Error error() const;
101
102 bool isDraining() const;
103
104 bool isActiveOrIdle() const;
105
106 // atomicaly checks if the state is stopped and marked as drained
107 std::pair<bool, bool> getDrainedAndStopped() const;
108
109 // waits if the method stop(error, true) has bee called
110 void waitForDrained(std::chrono::milliseconds timeout);
111
112 // mark as drained and wake up the method waitForDrained
113 void onDrained();
114
115 // Active/Idle/Suspended -> Stopped
116 Notifier stop(QAudio::Error error = QAudio::NoError, bool shouldDrain = false,
117 bool forceUpdateError = false);
118
119 // Active/Idle/Suspended -> Stopped
120 Notifier stopOrUpdateError(QAudio::Error error = QAudio::NoError)
121 {
122 return stop(error, shouldDrain: false, forceUpdateError: true);
123 }
124
125 // Stopped -> Active/Idle
126 Notifier start(bool isActive = true);
127
128 // Active/Idle -> Suspended + saves the exchanged state
129 Notifier suspend();
130
131 // Suspended -> saved state (Active/Idle)
132 Notifier resume();
133
134 // Idle -> Active
135 Notifier activateFromIdle();
136
137 // Active/Idle -> Active/Idle + updateError
138 Notifier updateActiveOrIdle(bool isActive, QAudio::Error error = QAudio::NoError);
139
140 // Any -> Any; better use more strict methods
141 Notifier forceSetState(QAudio::State state, QAudio::Error error = QAudio::NoError);
142
143 // force set the error
144 void setError(QAudio::Error error);
145
146private:
147 Notifier changeState(std::pair<RawState, uint32_t> prevStatesSet, RawState state,
148 QAudio::Error error = QAudio::NoError, bool shouldDrain = false);
149
150 void reset(RawState state, RawState prevState, QAudio::Error error);
151
152private:
153 QPointer<QAudioStateChangeNotifier> m_notifier;
154 std::atomic<RawState> m_state = QAudio::StoppedState;
155 std::atomic<QAudio::Error> m_error = QAudio::NoError;
156 RawState m_suspendedInState = QAudio::SuspendedState;
157
158 struct Synchronizer;
159 std::unique_ptr<Synchronizer> m_sychronizer;
160};
161
162QT_END_NAMESPACE
163
164#endif // QAUDIOSTATEMACHINE_P_H
165

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