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
qwavedecoder.cpp
Go to the documentation of this file.
1// Copyright (C) 2021 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// Qt-Security score:critical reason:data-parser
4
5#include "qwavedecoder.h"
6
7#include <QtCore/qdebug.h>
8#include <QtCore/qendian.h>
9#include <QtCore/qsysinfo.h>
10#include <QtCore/qtimer.h>
11#include <QtCore/qbytearray.h>
12
13#include <limits.h>
14
15#include <dr_wav.h>
16
17QT_BEGIN_NAMESPACE
18
19#if QT_DEPRECATED_SINCE(6, 11)
20
21QWaveDecoder::QWaveDecoder(QIODevice *device, QObject *parent)
22 : QIODevice(parent),
23 device(device)
24{
25}
26
27QWaveDecoder::QWaveDecoder(QIODevice *device, const QAudioFormat &format, QObject *parent)
28 : QIODevice(parent),
29 device(device),
30 format(format)
31{
32}
33
34QWaveDecoder::~QWaveDecoder()
35{
36 m_headerBuf.reset();
37}
38
39bool QWaveDecoder::open(QIODevice::OpenMode mode)
40{
41 bool canOpen = false;
42 if (mode & QIODevice::ReadOnly && mode & ~QIODevice::WriteOnly) {
43 canOpen = QIODevice::open(mode | QIODevice::Unbuffered);
44 if (canOpen) {
45 m_headerBuf = std::make_unique<QByteArray>();
46 connect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
47 // Try immediately if data already available
48 if (device->bytesAvailable() > 0)
49 handleData();
50 }
51 return canOpen;
52 }
53
54 if (mode & QIODevice::WriteOnly) {
55 if (format.sampleFormat() != QAudioFormat::Int16)
56 return false;
57 canOpen = QIODevice::open(mode);
58 if (canOpen && writeHeader())
59 haveHeader = true;
60 return canOpen;
61 }
62 return QIODevice::open(mode);
63}
64
65void QWaveDecoder::close()
66{
67 if (isOpen() && (openMode() & QIODevice::WriteOnly)) {
68 Q_ASSERT(dataSize < INT_MAX);
69 if (!device->isOpen() || !writeDataLength())
70 qWarning() << "Failed to finalize wav file";
71 }
72
73 m_headerBuf.reset();
74
75 QIODevice::close();
76}
77
78bool QWaveDecoder::seek(qint64 pos)
79{
80 return device->seek(pos);
81}
82
83qint64 QWaveDecoder::pos() const
84{
85 return device->pos();
86}
87
88void QWaveDecoder::setIODevice(QIODevice * /* device */)
89{
90}
91
92QAudioFormat QWaveDecoder::audioFormat() const
93{
94 return format;
95}
96
97QIODevice* QWaveDecoder::getDevice()
98{
99 return device;
100}
101
102int QWaveDecoder::duration() const
103{
104 if (openMode() & QIODevice::WriteOnly)
105 return 0;
106 int bytesPerSec = format.bytesPerFrame() * format.sampleRate();
107 return bytesPerSec ? size() * 1000 / bytesPerSec : 0;
108}
109
110qint64 QWaveDecoder::size() const
111{
112 if (openMode() & QIODevice::ReadOnly) {
113 if (!haveFormat)
114 return 0;
115 return dataSize;
116 } else {
117 return device->size();
118 }
119}
120
121bool QWaveDecoder::isSequential() const
122{
123 return device->isSequential();
124}
125
126qint64 QWaveDecoder::bytesAvailable() const
127{
128 return haveFormat ? device->bytesAvailable() : 0;
129}
130
131qint64 QWaveDecoder::headerLength()
132{
133 return HeaderLength;
134}
135
136qint64 QWaveDecoder::readData(char *data, qint64 maxlen)
137{
138 const int bytesPerSample = format.bytesPerSample();
139 if (!haveFormat || bytesPerSample == 0)
140 return 0;
141
142 // Align to sample boundary
143 maxlen = (maxlen / bytesPerSample) * bytesPerSample;
144 if (maxlen == 0)
145 return 0;
146
147 qint64 totalRead = 0;
148 char *dst = data;
149
150 // For sequential devices, drain the PCM prefix buffer first.
151 // This buffer holds bytes already consumed from the device during header parsing.
152 if (m_headerBuf && !m_headerBuf->isEmpty()) {
153 qint64 fromBuf = qMin(maxlen, qint64(m_headerBuf->size()));
154 // Align to sample boundary
155 fromBuf = (fromBuf / bytesPerSample) * bytesPerSample;
156 if (fromBuf > 0) {
157 memcpy(dst, m_headerBuf->constData(), size_t(fromBuf));
158 m_headerBuf->remove(0, static_cast<qsizetype>(fromBuf));
159 totalRead += fromBuf;
160 dst += fromBuf;
161 maxlen -= fromBuf;
162 }
163 if (m_headerBuf->isEmpty())
164 m_headerBuf.reset();
165 }
166
167 // Read remainder from device
168 if (maxlen > 0) {
169 qint64 read = device->read(dst, maxlen);
170 if (read > 0)
171 totalRead += read;
172 }
173
174 // Byte-swap the entire output for big-endian (RIFX) WAV on LE host (or vice versa)
175 if (m_byteSwap && format.bytesPerFrame() > 1 && totalRead > 0) {
176 qint64 nSamples = totalRead / bytesPerSample;
177 switch (bytesPerSample) {
178 case 2: qbswap<2>(data, qsizetype(nSamples), data); break;
179 case 4: qbswap<4>(data, qsizetype(nSamples), data); break;
180 default: Q_UNREACHABLE();
181 }
182 }
183
184 return totalRead;
185}
186
187qint64 QWaveDecoder::writeData(const char *data, qint64 len)
188{
189 if (!haveHeader)
190 return 0;
191 qint64 written = device->write(data, len);
192 dataSize += written;
193 return written;
194}
195
196bool QWaveDecoder::writeHeader()
197{
198 if (device->size() != 0)
199 return false;
200
201#ifndef Q_LITTLE_ENDIAN
202 return false;
203#endif
204
205 CombinedHeader header;
206 memset(&header, 0, HeaderLength);
207
208 memcpy(header.riff.descriptor.id, "RIFF", 4);
209 qToLittleEndian<quint32>(quint32(dataSize + HeaderLength - 8),
210 reinterpret_cast<unsigned char*>(&header.riff.descriptor.size));
211 memcpy(header.riff.type, "WAVE", 4);
212
213 memcpy(header.wave.descriptor.id, "fmt ", 4);
214 qToLittleEndian<quint32>(quint32(16),
215 reinterpret_cast<unsigned char*>(&header.wave.descriptor.size));
216 qToLittleEndian<quint16>(quint16(1),
217 reinterpret_cast<unsigned char*>(&header.wave.audioFormat));
218 qToLittleEndian<quint16>(quint16(format.channelCount()),
219 reinterpret_cast<unsigned char*>(&header.wave.numChannels));
220 qToLittleEndian<quint32>(quint32(format.sampleRate()),
221 reinterpret_cast<unsigned char*>(&header.wave.sampleRate));
222 qToLittleEndian<quint32>(quint32(format.sampleRate() * format.bytesPerFrame()),
223 reinterpret_cast<unsigned char*>(&header.wave.byteRate));
224 qToLittleEndian<quint16>(quint16(format.channelCount() * format.bytesPerSample()),
225 reinterpret_cast<unsigned char*>(&header.wave.blockAlign));
226 qToLittleEndian<quint16>(quint16(format.bytesPerSample() * 8),
227 reinterpret_cast<unsigned char*>(&header.wave.bitsPerSample));
228
229 memcpy(header.data.descriptor.id, "data", 4);
230 qToLittleEndian<quint32>(quint32(dataSize),
231 reinterpret_cast<unsigned char*>(&header.data.descriptor.size));
232
233 return device->write(reinterpret_cast<const char *>(&header), HeaderLength);
234}
235
236bool QWaveDecoder::writeDataLength()
237{
238#ifndef Q_LITTLE_ENDIAN
239 return false;
240#endif
241 if (isSequential())
242 return false;
243
244 if (!device->seek(4)) {
245 qDebug() << "can't seek";
246 return false;
247 }
248
249 quint32 length = quint32(dataSize + HeaderLength - 8);
250 if (device->write(reinterpret_cast<const char *>(&length), 4) != 4)
251 return false;
252
253 if (!device->seek(40))
254 return false;
255
256 return device->write(reinterpret_cast<const char *>(&dataSize), 4);
257}
258
259void QWaveDecoder::parsingFailed()
260{
261 m_headerBuf.reset();
262
263 Q_ASSERT(device);
264 disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
265 emit parsingError();
266}
267
268void QWaveDecoder::handleData()
269{
270 using namespace QtPrivate;
271
272 if (openMode() == QIODevice::WriteOnly)
273 return;
274
275 if (haveFormat) {
276 // Already parsed — relay readyRead from device to our listeners
277 disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
278 connect(device, &QIODevice::readyRead, this, &QIODevice::readyRead);
279 return;
280 }
281
282 if (!m_headerBuf)
283 return;
284
285 // Accumulate all available bytes into the header buffer
286 QByteArray incoming = device->readAll();
287 if (!incoming.isEmpty())
288 m_headerBuf->append(incoming);
289
290 // Need at least the RIFF header + fmt chunk to attempt parsing
291 if (m_headerBuf->size() < int(sizeof(RIFFHeader)) + int(sizeof(chunk)))
292 return;
293
294 // Try to parse the accumulated buffer with dr_wav in-memory mode
295 drwav wav;
296 if (!drwav_init_memory(&wav, m_headerBuf->constData(), size_t(m_headerBuf->size()), nullptr)) {
297 // Not enough data yet — wait for more readyRead signals
298 // (but only if the device isn't done)
299 if (device->atEnd())
300 parsingFailed();
301 return;
302 }
303
304 // dr_wav parsed the header successfully. Extract what we need.
305 drwav_uint16 audioFormat = drwav_fmt_get_format(&wav.fmt);
306
307 if (audioFormat != 0 && audioFormat != 1) {
308 // Not PCM (e.g. float, ADPCM, extensible) — reject
309 drwav_uninit(&wav);
310 parsingFailed();
311 return;
312 }
313
314 int bitsPerSample = wav.bitsPerSample;
315 int sampleRate = int(wav.sampleRate);
316 int channels = int(wav.channels);
317
318 // Only 8-bit and 16-bit PCM supported
319 QAudioFormat::SampleFormat fmt = QAudioFormat::Unknown;
320 switch (bitsPerSample) {
321 case 8: fmt = QAudioFormat::UInt8; break;
322 case 16: fmt = QAudioFormat::Int16; break;
323 default: break; // 24-bit, 32-bit, float — rejected
324 }
325
326 if (fmt == QAudioFormat::Unknown || sampleRate == 0 || channels == 0) {
327 drwav_uninit(&wav);
328 parsingFailed();
329 return;
330 }
331
332 // Endianness: RIFX = big-endian container
333 bool bigEndian = (wav.container == drwav_container_rifx);
334 m_byteSwap = (bigEndian != (QSysInfo::ByteOrder == QSysInfo::BigEndian));
335
336 qint64 dataChunkDataPos = qint64(wav.dataChunkDataPos);
337 dataSize = qint64(wav.dataChunkDataSize);
338
339 drwav_uninit(&wav);
340
341 // Seek the device to the PCM data start
342 if (!device->isSequential()) {
343 if (!device->seek(dataChunkDataPos)) {
344 parsingFailed();
345 return;
346 }
347 // No longer need the header buffer for non-sequential
348 m_headerBuf.reset();
349 } else {
350 // Sequential device: m_headerBuf already holds all bytes consumed so far.
351 // Bytes [0..dataChunkDataPos-1] = WAV header.
352 // Bytes [dataChunkDataPos..m_headerBuf->size()-1] = PCM prefix already read.
353 // Trim the buffer to keep only the PCM prefix.
354 if (dataChunkDataPos < m_headerBuf->size()) {
355 *m_headerBuf = m_headerBuf->mid(qsizetype(dataChunkDataPos));
356 } else {
357 m_headerBuf->clear();
358 }
359 }
360
361 format.setSampleFormat(fmt);
362 format.setSampleRate(sampleRate);
363 format.setChannelCount(channels);
364
365 if (!dataSize)
366 dataSize = device->size() - dataChunkDataPos;
367
368 haveFormat = true;
369 disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
370 connect(device, &QIODevice::readyRead, this, &QIODevice::readyRead);
371 emit formatKnown();
372}
373
374#endif // QT_DEPRECATED_SINCE(6, 11)
375
376QT_END_NAMESPACE
377
378#include "moc_qwavedecoder.cpp"