1// Copyright (C) 2024 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//
5// W A R N I N G
6// -------------
7//
8// This file is not part of the Qt API. It exists for the convenience
9// of a number of Qt sources files. This header file may change from
10// version to version without notice, or even be removed.
11//
12// We mean it.
13//
14
15#ifndef QAUDIORINGBUFFER_P_H
16#define QAUDIORINGBUFFER_P_H
17
18#include <QtCore/qspan.h>
19#include <QtCore/qtclasshelpermacros.h>
20#include <QtMultimedia/private/qaudio_qspan_support_p.h>
21
22#include <algorithm>
23#include <atomic>
24#include <cstdlib>
25#include <limits>
26#include <type_traits>
27
28QT_BEGIN_NAMESPACE
29
30namespace QtPrivate {
31
32// Single-producer, single-consumer wait-free queue
33template <typename T>
34class QAudioRingBuffer
35{
36 static constexpr bool isTriviallyDestructible = std::is_trivially_destructible_v<T>;
37
38public:
39 using ValueType = T;
40 using Region = QSpan<T>;
41 using ConstRegion = QSpan<const T>;
42
43 explicit QAudioRingBuffer(int bufferSize) : m_bufferSize(bufferSize)
44 {
45 if (bufferSize)
46 m_buffer = reinterpret_cast<T *>(
47 ::operator new(sizeof(T) * bufferSize, std::align_val_t(alignof(T))));
48 }
49
50 Q_DISABLE_COPY_MOVE(QAudioRingBuffer)
51
52 ~QAudioRingBuffer()
53 {
54 if constexpr (!isTriviallyDestructible) {
55 consumeAll([](auto /* elements*/) {
56 });
57 }
58
59 ::operator delete(reinterpret_cast<void *>(m_buffer), std::align_val_t(alignof(T)));
60 }
61
62 int write(ConstRegion region)
63 {
64 using namespace QtMultimediaPrivate; // drop
65 return produceSome([&](Region writeRegion) {
66 qsizetype writeSize = std::min(region.size(), writeRegion.size());
67 std::uninitialized_copy_n(region.data(), writeSize, writeRegion.data());
68 region = drop(region, writeSize);
69
70 return Region{
71 writeRegion.data(),
72 writeSize,
73 };
74 });
75 }
76
77 bool write(T element)
78 {
79 return produceOne([&] {
80 return std::move(element);
81 });
82 }
83
84 template <typename Functor>
85 bool produceOne(Functor &&producer)
86#ifdef __cpp_concepts
87 requires
88 std::is_invocable_v<Functor> &&std::is_constructible_v<T, std::invoke_result_t<Functor>>
89#endif
90 {
91 Region writeRegion = acquireWriteRegion(size: 1);
92 if (writeRegion.isEmpty())
93 return false;
94 T *writeElement = writeRegion.data();
95 new (writeElement) T{ producer() };
96 releaseWriteRegion(elementsRead: 1);
97 return true;
98 }
99
100 template <typename Functor>
101 int produceSome(Functor &&producer, int elements = std::numeric_limits<int>::max())
102#ifdef __cpp_concepts
103 requires std::is_invocable_v<Functor, Region>
104 &&std::is_same_v<std::invoke_result_t<Functor, Region>, Region>
105#endif
106 {
107 int elementsRemain = elements;
108 int elementsWritten = 0;
109 while (elementsRemain) {
110 Region writeRegion = acquireWriteRegion(size: elementsRemain);
111 if (writeRegion.isEmpty())
112 break;
113
114 Region writtenRegion = producer(writeRegion);
115 if (writtenRegion.isEmpty())
116 break;
117
118 Q_ASSERT(writtenRegion.data() == writeRegion.data());
119 Q_ASSERT(writtenRegion.size() <= writeRegion.size());
120
121 elementsRemain -= writtenRegion.size();
122 elementsWritten += writtenRegion.size();
123 releaseWriteRegion(elementsRead: writtenRegion.size());
124 }
125 return elementsWritten;
126 }
127
128 template <typename Functor>
129 int consumeAll(Functor &&consumer)
130 {
131 return consumeSome([&](Region region) {
132 consumer(region);
133 return region;
134 });
135 }
136
137 template <typename Functor>
138 int consume(int elements, Functor &&consumer)
139 {
140 int remainingElemnts = elements;
141 return consumeSome([&](Region region) {
142 if (remainingElemnts == 0)
143 return Region{};
144
145 Region regionToConsume = QtMultimediaPrivate::take(region, remainingElemnts);
146 consumer(regionToConsume);
147 remainingElemnts -= regionToConsume.size();
148 return regionToConsume;
149 });
150 }
151
152 // consumer has to return the region it has consumed
153 template <typename Functor>
154 int consumeSome(Functor &&consumer, int elements = std::numeric_limits<int>::max())
155#ifdef __cpp_concepts
156 requires std::is_invocable_v<Functor, Region>
157 &&std::is_same_v<std::invoke_result_t<Functor, Region>, Region>
158#endif
159 {
160 int elementsConsumed = 0;
161
162 while (elements > elementsConsumed) {
163 Region readRegion = acquireReadRegion(size: elements - elementsConsumed);
164 if (readRegion.isEmpty())
165 break;
166
167 Region consumedRegion = consumer(readRegion);
168 if (consumedRegion.isEmpty())
169 break;
170 Q_ASSERT(consumedRegion.data() == readRegion.data());
171 Q_ASSERT(consumedRegion.size() <= readRegion.size());
172
173 if constexpr (!isTriviallyDestructible)
174 std::destroy(consumedRegion.begin(), consumedRegion.end());
175
176 elementsConsumed += consumedRegion.size();
177 releaseReadRegion(elementsWritten: consumedRegion.size());
178 }
179
180 return elementsConsumed;
181 }
182
183 // CAVEAT: beware of the thread safety
184 int used() const { return m_bufferUsed.load(m: std::memory_order_relaxed); }
185 int free() const { return m_bufferSize - m_bufferUsed.load(m: std::memory_order_relaxed); }
186
187 int size() const { return m_bufferSize; }
188
189 void reset()
190#ifdef __cpp_concepts
191 requires isTriviallyDestructible
192#endif
193 {
194 m_readPos = 0;
195 m_writePos = 0;
196 m_bufferUsed.store(i: 0, m: std::memory_order_relaxed);
197 }
198
199private:
200 Region acquireWriteRegion(int size)
201 {
202 const int free = m_bufferSize - m_bufferUsed.load(m: std::memory_order_acquire);
203
204 Region output;
205 if (free > 0) {
206 const int writeSize = qMin(a: size, b: qMin(a: m_bufferSize - m_writePos, b: free));
207 output = writeSize > 0 ? Region(m_buffer + m_writePos, writeSize) : Region();
208 } else {
209 output = Region();
210 }
211 return output;
212 }
213
214 void releaseWriteRegion(int elementsRead)
215 {
216 m_writePos = (m_writePos + elementsRead) % m_bufferSize;
217 m_bufferUsed.fetch_add(i: elementsRead, m: std::memory_order_release);
218 }
219
220 Region acquireReadRegion(int size)
221 {
222 const int used = m_bufferUsed.load(m: std::memory_order_acquire);
223
224 if (used > 0) {
225 const int readSize = qMin(a: size, b: qMin(a: m_bufferSize - m_readPos, b: used));
226 return readSize > 0 ? Region(m_buffer + m_readPos, readSize) : Region();
227 }
228
229 return Region();
230 }
231
232 // WARNING: we need to ensure that all elements are destroyed
233 void releaseReadRegion(int elementsWritten)
234 {
235 m_readPos = (m_readPos + elementsWritten) % m_bufferSize;
236 m_bufferUsed.fetch_sub(i: elementsWritten, m: std::memory_order_release);
237 }
238
239 const int m_bufferSize;
240 int m_readPos{};
241 int m_writePos{};
242 T *m_buffer{};
243 std::atomic_int m_bufferUsed{};
244};
245
246} // namespace QtPrivate
247
248QT_END_NAMESPACE
249
250#endif // QAUDIORINGBUFFER_P_H
251

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