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_QIODEVICE_SUPPORT_P_H
5#define QAUDIO_QIODEVICE_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 <QtCore/qdebug.h>
19#include <QtCore/qglobal.h>
20#include <QtCore/qiodevice.h>
21#include <QtCore/qmutex.h>
22#include <QtCore/qspan.h>
23
24#include <QtMultimedia/private/qaudio_alignment_support_p.h>
25#include <QtMultimedia/private/qaudio_qspan_support_p.h>
26#include <QtMultimedia/private/qaudioringbuffer_p.h>
27#include <QtMultimedia/private/qautoresetevent_p.h>
28
29#include <deque>
30#include <mutex>
31
32QT_BEGIN_NAMESPACE
33
34namespace QtPrivate {
35
36class QIODeviceRingBufferWriterBase : public QIODevice
37{
38public:
39 explicit QIODeviceRingBufferWriterBase(QObject *parent = nullptr) : QIODevice(parent)
40 {
41 setOpenMode(QIODevice::WriteOnly | QIODevice::Unbuffered);
42
43 m_bytesConsumed.callOnActivated(functor: [&] {
44 qint64 bytes = m_bytesConsumedFromRingbuffer.exchange(i: 0, m: std::memory_order_relaxed);
45 if (bytes > 0)
46 emit bytesWritten(bytes);
47 });
48 }
49
50 void bytesConsumedFromRingbuffer(qint64 bytes)
51 {
52 m_bytesConsumedFromRingbuffer.fetch_add(i: bytes, m: std::memory_order_relaxed);
53 m_bytesConsumed.set();
54 }
55
56 bool isSequential() const override { return true; }
57
58private:
59 QtPrivate::QAutoResetEvent m_bytesConsumed;
60 std::atomic<qint64> m_bytesConsumedFromRingbuffer{ 0 };
61};
62
63// QIODevice writing to a QAudioRingBuffer
64template <typename SampleType>
65class QIODeviceRingBufferWriter final : public QIODeviceRingBufferWriterBase
66{
67public:
68 using Ringbuffer = QtPrivate::QAudioRingBuffer<SampleType>;
69
70 explicit QIODeviceRingBufferWriter(Ringbuffer *rb, QObject *parent = nullptr)
71 : QIODeviceRingBufferWriterBase(parent), m_ringbuffer(rb)
72 {
73 Q_ASSERT(rb);
74 }
75
76 qint64 readData(char * /*data*/, qint64 /*maxlen*/) override { return -1; }
77 qint64 writeData(const char *data, qint64 len) override
78 {
79 using namespace QtMultimediaPrivate; // take/drop
80
81 // we don't write fractional samples
82 int64_t usableLength = alignDown(arg: len, alignment: sizeof(SampleType));
83 auto readRegion = QSpan<const SampleType>{
84 reinterpret_cast<const SampleType *>(data),
85 qsizetype(usableLength / sizeof(SampleType)),
86 };
87
88 qint64 bytesWritten = m_ringbuffer->write(readRegion) * sizeof(SampleType);
89 if (bytesWritten)
90 emit readyRead();
91
92 return bytesWritten;
93 }
94
95 qint64 bytesToWrite() const override { return m_ringbuffer->free() * sizeof(SampleType); }
96
97private:
98 Ringbuffer *const m_ringbuffer;
99};
100
101// QIODevice reading from a QAudioRingBuffer
102template <typename SampleType>
103class QIODeviceRingBufferReader final : public QIODevice
104{
105public:
106 using Ringbuffer = QtPrivate::QAudioRingBuffer<SampleType>;
107
108 explicit QIODeviceRingBufferReader(Ringbuffer *rb, QObject *parent = nullptr)
109 : QIODevice(parent), m_ringbuffer(rb)
110 {
111 Q_ASSERT(rb);
112 }
113
114 qint64 readData(char *data, qint64 maxlen) override
115 {
116 using namespace QtMultimediaPrivate; // drop
117
118 QSpan<std::byte> outputRegion = as_writable_bytes(s: QSpan{ data, qsizetype(maxlen) });
119
120 qsizetype maxSizeToRead = outputRegion.size_bytes() / sizeof(SampleType);
121
122 int samplesConsumed = m_ringbuffer->consumeSome([&](auto readRegion) {
123 QSpan readByteRegion = as_bytes(readRegion);
124 std::copy(readByteRegion.begin(), readByteRegion.end(), outputRegion.begin());
125 outputRegion = drop(outputRegion, readByteRegion.size());
126
127 return readRegion;
128 }, maxSizeToRead);
129
130 return samplesConsumed * sizeof(SampleType);
131 }
132
133 qint64 writeData(const char * /*data*/, qint64 /*len*/) override { return -1; }
134 qint64 bytesAvailable() const override { return m_ringbuffer->used() * sizeof(SampleType); }
135 bool isSequential() const override { return true; }
136
137private:
138 Ringbuffer *const m_ringbuffer;
139};
140
141// QIODevice backed by a std::deque
142class QDequeIODevice final : public QIODevice
143{
144public:
145 using Deque = std::deque<char>;
146
147 explicit QDequeIODevice(QObject *parent = nullptr) : QIODevice(parent) { }
148
149 qint64 bytesAvailable() const override { return qint64(m_deque.size()); }
150
151private:
152 qint64 readData(char *data, qint64 maxlen) override
153 {
154 std::lock_guard guard{ m_mutex };
155
156 size_t bytesToRead = std::min<size_t>(a: m_deque.size(), b: maxlen);
157 std::copy_n(first: m_deque.begin(), n: bytesToRead, result: data);
158
159 m_deque.erase(first: m_deque.begin(), last: m_deque.begin() + bytesToRead);
160 return qint64(bytesToRead);
161 }
162
163 qint64 writeData(const char *data, qint64 len) override
164 {
165 std::lock_guard guard{ m_mutex };
166 m_deque.insert(position: m_deque.end(), first: data, last: data + len);
167 return len;
168 }
169
170 QMutex m_mutex;
171 Deque m_deque;
172};
173
174inline qint64 writeToDevice(QIODevice &device, QSpan<const std::byte> data)
175{
176 return device.write(data: reinterpret_cast<const char *>(data.data()), len: data.size());
177}
178
179inline qint64 readFromDevice(QIODevice &device, QSpan<std::byte> outputBuffer)
180{
181 return device.read(data: reinterpret_cast<char *>(outputBuffer.data()), maxlen: outputBuffer.size());
182}
183
184template <typename SampleType>
185qsizetype pullFromQIODeviceToRingbuffer(QIODevice &device, QAudioRingBuffer<SampleType> &ringbuffer)
186{
187 using namespace QtMultimediaPrivate;
188
189 int totalSamplesWritten = ringbuffer.produceSome([&](QSpan<SampleType> writeRegion) {
190 qint64 bytesAvailableInDevice = alignDown(arg: device.bytesAvailable(), alignment: sizeof(SampleType));
191 if (!bytesAvailableInDevice)
192 return QSpan<SampleType>{}; // no data in iodevice
193
194 qint64 samplesAvailableInDevice = bytesAvailableInDevice / sizeof(SampleType);
195 writeRegion = take(writeRegion, samplesAvailableInDevice);
196
197 qint64 bytesRead = readFromDevice(device, as_writable_bytes(writeRegion));
198 if (bytesRead < 0) {
199 qWarning() << "pullFromQIODeviceToRingbuffer cannot read from QIODevice:"
200 << device.errorString();
201 return QSpan<SampleType>{};
202 }
203
204 return take(writeRegion, bytesRead / sizeof(SampleType));
205 });
206
207 return totalSamplesWritten * sizeof(SampleType);
208}
209
210template <typename SampleType>
211qsizetype pushToQIODeviceFromRingbuffer(QIODevice &device, QAudioRingBuffer<SampleType> &ringbuffer)
212{
213 using namespace QtMultimediaPrivate;
214
215 int totalSamplesWritten = ringbuffer.consumeSome([&](QSpan<SampleType> region) {
216 // we do our best effort and only push full samples to the device
217 const quint64 bytesToWrite = [&] {
218 const qint64 deviceBytesToWrite = device.bytesToWrite();
219 return (deviceBytesToWrite > 0) ? alignDown(arg: deviceBytesToWrite, alignment: sizeof(SampleType))
220 : region.size_bytes();
221 }();
222
223 QSpan<const std::byte> bufferByteRegion = take(as_bytes(region), bytesToWrite);
224 int bytesWritten = writeToDevice(device, data: bufferByteRegion);
225 if (bytesWritten < 0) {
226 qWarning() << "pushToQIODeviceFromRingbuffer cannot push data to QIODevice:"
227 << device.errorString();
228 return QSpan<SampleType>{};
229 }
230 Q_ASSERT(isAligned(bytesWritten, sizeof(SampleType)));
231 int samplesWritten = bytesWritten / sizeof(SampleType);
232 return take(region, samplesWritten);
233 });
234
235 return totalSamplesWritten * sizeof(SampleType);
236}
237
238} // namespace QtPrivate
239
240QT_END_NAMESPACE
241
242#endif // QAUDIO_QIODEVICE_SUPPORT_P_H
243

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