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 <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
37{
38public:
39 explicit QIODeviceRingBufferWriterBase(QObject *parent = nullptr) : QIODevice(parent)
40 {
41 setOpenMode(QIODevice::WriteOnly | QIODevice::Unbuffered);
42
43 m_bytesConsumed.callOnActivated([&] {
44 qint64 bytes = m_bytesConsumedFromRingbuffer.exchange(0, 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(bytes, 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>
66{
67public:
69
70 explicit QIODeviceRingBufferWriter(Ringbuffer *rb, QObject *parent = nullptr)
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(len, 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>
104{
105public:
107
108 explicit QIODeviceRingBufferReader(Ringbuffer *rb, QObject *parent = nullptr)
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(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>(m_deque.size(), maxlen);
157 std::copy_n(m_deque.begin(), bytesToRead, data);
158
159 m_deque.erase(m_deque.begin(), 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(m_deque.end(), data, 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(reinterpret_cast<const char *>(data.data()), data.size());
177}
178
179inline qint64 readFromDevice(QIODevice &device, QSpan<std::byte> outputBuffer)
180{
181 return device.read(reinterpret_cast<char *>(outputBuffer.data()), 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(device.bytesAvailable(), 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(deviceBytesToWrite, sizeof(SampleType))
220 : region.size_bytes();
221 }();
222
223 QSpan<const std::byte> bufferByteRegion = take(as_bytes(region), bytesToWrite);
224 int bytesWritten = writeToDevice(device, 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
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)