Source code

Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:critical reason:data-parser
#include "qwavedecoder.h"
#include <QtCore/qdebug.h>
#include <QtCore/qendian.h>
#include <QtCore/qsysinfo.h>
#include <QtCore/qtimer.h>
#include <QtCore/qbytearray.h>
#include <limits.h>
#include <dr_wav.h>
QT_BEGIN_NAMESPACE
#if QT_DEPRECATED_SINCE(6, 11)
namespace QtMultimediaPrivate {
class QWaveDecoderOldLayout : public QIODevice
{
enum State { InitialState, WaitingForFormatState, WaitingForDataState };
public:
bool haveFormat = false;
bool haveHeader = false;
qint64 dataSize = 0;
QIODevice *device = nullptr;
QAudioFormat format;
State state = InitialState;
quint32 junkToSkip = 0;
bool bigEndian = false;
bool byteSwap = false;
int bps = 0;
};
static_assert(sizeof(QWaveDecoder) == sizeof(QtMultimediaPrivate::QWaveDecoderOldLayout),
"QWaveDecoder ABI size mismatch — adjust fields or QWaveDecoderNewFields");
} // namespace QtMultimediaPrivate
QWaveDecoder::QWaveDecoder(QIODevice *device, QObject *parent)
: QIODevice(parent),
device(device)
{
}
QWaveDecoder::QWaveDecoder(QIODevice *device, const QAudioFormat &format, QObject *parent)
: QIODevice(parent),
device(device),
format(format)
{
}
QWaveDecoder::~QWaveDecoder()
{
m_headerBuf.reset();
}
bool QWaveDecoder::open(QIODevice::OpenMode mode)
{
bool canOpen = false;
if (mode & QIODevice::ReadOnly && mode & ~QIODevice::WriteOnly) {
canOpen = QIODevice::open(mode | QIODevice::Unbuffered);
if (canOpen) {
m_headerBuf = std::make_unique<QByteArray>();
connect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
// Try immediately if data already available
if (device->bytesAvailable() > 0)
handleData();
}
return canOpen;
}
if (mode & QIODevice::WriteOnly) {
if (format.sampleFormat() != QAudioFormat::Int16)
return false;
canOpen = QIODevice::open(mode);
if (canOpen && writeHeader())
haveHeader = true;
return canOpen;
}
return QIODevice::open(mode);
}
void QWaveDecoder::close()
{
if (isOpen() && (openMode() & QIODevice::WriteOnly)) {
Q_ASSERT(dataSize < INT_MAX);
if (!device->isOpen() || !writeDataLength())
qWarning() << "Failed to finalize wav file";
}
m_headerBuf.reset();
QIODevice::close();
}
bool QWaveDecoder::seek(qint64 pos)
{
return device->seek(pos);
}
qint64 QWaveDecoder::pos() const
{
return device->pos();
}
void QWaveDecoder::setIODevice(QIODevice * /* device */)
{
}
QAudioFormat QWaveDecoder::audioFormat() const
{
return format;
}
QIODevice* QWaveDecoder::getDevice()
{
return device;
}
int QWaveDecoder::duration() const
{
if (openMode() & QIODevice::WriteOnly)
return 0;
int bytesPerSec = format.bytesPerFrame() * format.sampleRate();
return bytesPerSec ? size() * 1000 / bytesPerSec : 0;
}
qint64 QWaveDecoder::size() const
{
if (openMode() & QIODevice::ReadOnly) {
if (!haveFormat)
return 0;
return dataSize;
} else {
return device->size();
}
}
bool QWaveDecoder::isSequential() const
{
return device->isSequential();
}
qint64 QWaveDecoder::bytesAvailable() const
{
return haveFormat ? device->bytesAvailable() : 0;
}
qint64 QWaveDecoder::headerLength()
{
return HeaderLength;
}
qint64 QWaveDecoder::readData(char *data, qint64 maxlen)
{
const int bytesPerSample = format.bytesPerSample();
if (!haveFormat || bytesPerSample == 0)
return 0;
// Align to sample boundary
maxlen = (maxlen / bytesPerSample) * bytesPerSample;
if (maxlen == 0)
return 0;
qint64 totalRead = 0;
char *dst = data;
// For sequential devices, drain the PCM prefix buffer first.
// This buffer holds bytes already consumed from the device during header parsing.
if (m_headerBuf && !m_headerBuf->isEmpty()) {
qint64 fromBuf = qMin(maxlen, qint64(m_headerBuf->size()));
// Align to sample boundary
fromBuf = (fromBuf / bytesPerSample) * bytesPerSample;
if (fromBuf > 0) {
memcpy(dst, m_headerBuf->constData(), size_t(fromBuf));
m_headerBuf->remove(0, static_cast<qsizetype>(fromBuf));
totalRead += fromBuf;
dst += fromBuf;
maxlen -= fromBuf;
}
if (m_headerBuf->isEmpty())
m_headerBuf.reset();
}
// Read remainder from device
if (maxlen > 0) {
qint64 read = device->read(dst, maxlen);
if (read > 0)
totalRead += read;
}
// Byte-swap the entire output for big-endian (RIFX) WAV on LE host (or vice versa)
if (m_byteSwap && format.bytesPerFrame() > 1 && totalRead > 0) {
qint64 nSamples = totalRead / bytesPerSample;
switch (bytesPerSample) {
case 2: qbswap<2>(data, qsizetype(nSamples), data); break;
case 4: qbswap<4>(data, qsizetype(nSamples), data); break;
default: Q_UNREACHABLE();
}
}
return totalRead;
}
qint64 QWaveDecoder::writeData(const char *data, qint64 len)
{
if (!haveHeader)
return 0;
qint64 written = device->write(data, len);
dataSize += written;
return written;
}
bool QWaveDecoder::writeHeader()
{
if (device->size() != 0)
return false;
#ifndef Q_LITTLE_ENDIAN
return false;
#endif
CombinedHeader header;
memset(&header, 0, HeaderLength);
memcpy(header.riff.descriptor.id, "RIFF", 4);
qToLittleEndian<quint32>(quint32(dataSize + HeaderLength - 8),
reinterpret_cast<unsigned char*>(&header.riff.descriptor.size));
memcpy(header.riff.type, "WAVE", 4);
memcpy(header.wave.descriptor.id, "fmt ", 4);
qToLittleEndian<quint32>(quint32(16),
reinterpret_cast<unsigned char*>(&header.wave.descriptor.size));
qToLittleEndian<quint16>(quint16(1),
reinterpret_cast<unsigned char*>(&header.wave.audioFormat));
qToLittleEndian<quint16>(quint16(format.channelCount()),
reinterpret_cast<unsigned char*>(&header.wave.numChannels));
qToLittleEndian<quint32>(quint32(format.sampleRate()),
reinterpret_cast<unsigned char*>(&header.wave.sampleRate));
qToLittleEndian<quint32>(quint32(format.sampleRate() * format.bytesPerFrame()),
reinterpret_cast<unsigned char*>(&header.wave.byteRate));
qToLittleEndian<quint16>(quint16(format.channelCount() * format.bytesPerSample()),
reinterpret_cast<unsigned char*>(&header.wave.blockAlign));
qToLittleEndian<quint16>(quint16(format.bytesPerSample() * 8),
reinterpret_cast<unsigned char*>(&header.wave.bitsPerSample));
memcpy(header.data.descriptor.id, "data", 4);
qToLittleEndian<quint32>(quint32(dataSize),
reinterpret_cast<unsigned char*>(&header.data.descriptor.size));
return device->write(reinterpret_cast<const char *>(&header), HeaderLength);
}
bool QWaveDecoder::writeDataLength()
{
#ifndef Q_LITTLE_ENDIAN
return false;
#endif
if (isSequential())
return false;
if (!device->seek(4)) {
qDebug() << "can't seek";
return false;
}
quint32 length = quint32(dataSize + HeaderLength - 8);
if (device->write(reinterpret_cast<const char *>(&length), 4) != 4)
return false;
if (!device->seek(40))
return false;
return device->write(reinterpret_cast<const char *>(&dataSize), 4);
}
void QWaveDecoder::parsingFailed()
{
m_headerBuf.reset();
Q_ASSERT(device);
disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
Q_EMIT parsingError();
}
void QWaveDecoder::handleData()
{
using namespace QtPrivate;
if (openMode() == QIODevice::WriteOnly)
return;
if (haveFormat) {
// Already parsed — relay readyRead from device to our listeners
disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
connect(device, &QIODevice::readyRead, this, &QIODevice::readyRead);
return;
}
if (!m_headerBuf)
return;
// Accumulate all available bytes into the header buffer
QByteArray incoming = device->readAll();
if (!incoming.isEmpty())
m_headerBuf->append(incoming);
// Need at least the RIFF header + fmt chunk to attempt parsing
if (m_headerBuf->size() < int(sizeof(RIFFHeader)) + int(sizeof(chunk)))
return;
// Try to parse the accumulated buffer with dr_wav in-memory mode
drwav wav;
if (!drwav_init_memory(&wav, m_headerBuf->constData(), size_t(m_headerBuf->size()), nullptr)) {
// Not enough data yet — wait for more readyRead signals
// (but only if the device isn't done)
if (device->atEnd())
parsingFailed();
return;
}
// dr_wav parsed the header successfully. Extract what we need.
drwav_uint16 audioFormat = drwav_fmt_get_format(&wav.fmt);
if (audioFormat != 0 && audioFormat != 1) {
// Not PCM (e.g. float, ADPCM, extensible) — reject
drwav_uninit(&wav);
parsingFailed();
return;
}
int bitsPerSample = wav.bitsPerSample;
int sampleRate = int(wav.sampleRate);
int channels = int(wav.channels);
// Only 8-bit and 16-bit PCM supported
QAudioFormat::SampleFormat fmt = QAudioFormat::Unknown;
switch (bitsPerSample) {
case 8: fmt = QAudioFormat::UInt8; break;
case 16: fmt = QAudioFormat::Int16; break;
default: break; // 24-bit, 32-bit, float — rejected
}
if (fmt == QAudioFormat::Unknown || sampleRate == 0 || channels == 0) {
drwav_uninit(&wav);
parsingFailed();
return;
}
// Endianness: RIFX = big-endian container
bool bigEndian = (wav.container == drwav_container_rifx);
m_byteSwap = (bigEndian != (QSysInfo::ByteOrder == QSysInfo::BigEndian));
qint64 dataChunkDataPos = qint64(wav.dataChunkDataPos);
dataSize = qint64(wav.dataChunkDataSize);
drwav_uninit(&wav);
// Seek the device to the PCM data start
if (!device->isSequential()) {
if (!device->seek(dataChunkDataPos)) {
parsingFailed();
return;
}
// No longer need the header buffer for non-sequential
m_headerBuf.reset();
} else {
// Sequential device: m_headerBuf already holds all bytes consumed so far.
// Bytes [0..dataChunkDataPos-1] = WAV header.
// Bytes [dataChunkDataPos..m_headerBuf->size()-1] = PCM prefix already read.
// Trim the buffer to keep only the PCM prefix.
if (dataChunkDataPos < m_headerBuf->size()) {
*m_headerBuf = m_headerBuf->mid(qsizetype(dataChunkDataPos));
} else {
m_headerBuf->clear();
}
}
format.setSampleFormat(fmt);
format.setSampleRate(sampleRate);
format.setChannelCount(channels);
if (!dataSize)
dataSize = device->size() - dataChunkDataPos;
haveFormat = true;
disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
connect(device, &QIODevice::readyRead, this, &QIODevice::readyRead);
Q_EMIT formatKnown();
}
#endif // QT_DEPRECATED_SINCE(6, 11)
QT_END_NAMESPACE
#include "moc_qwavedecoder.cpp"