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