summaryrefslogtreecommitdiffstats
path: root/src/multimedia/audio/qwavedecoder.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/multimedia/audio/qwavedecoder.cpp')
-rw-r--r--src/multimedia/audio/qwavedecoder.cpp507
1 files changed, 507 insertions, 0 deletions
diff --git a/src/multimedia/audio/qwavedecoder.cpp b/src/multimedia/audio/qwavedecoder.cpp
new file mode 100644
index 000000000..452363ddc
--- /dev/null
+++ b/src/multimedia/audio/qwavedecoder.cpp
@@ -0,0 +1,507 @@
+// 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
+
+#include "qwavedecoder.h"
+
+#include <QtCore/qtimer.h>
+#include <QtCore/qendian.h>
+#include <limits.h>
+#include <qdebug.h>
+
+QT_BEGIN_NAMESPACE
+
+namespace {
+
+void bswap2(char *data, qsizetype count) noexcept
+{
+ for (qsizetype i = 0; i < count; ++i) {
+ qSwap(data[0], data[1]);
+ ++count;
+ data += 2;
+ }
+}
+
+void bswap4(char *data, qsizetype count) noexcept
+{
+ for (qsizetype i = 0; i < count; ++i) {
+ qSwap(data[0], data[3]);
+ qSwap(data[1], data[2]);
+ ++count;
+ data += 4;
+ }
+}
+
+}
+
+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() = default;
+
+bool QWaveDecoder::open(QIODevice::OpenMode mode)
+{
+ bool canOpen = false;
+ if (mode & QIODevice::ReadOnly && mode & ~QIODevice::WriteOnly) {
+ canOpen = QIODevice::open(mode | QIODevice::Unbuffered);
+ if (canOpen && enoughDataAvailable())
+ handleData();
+ else
+ connect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
+ return canOpen;
+ }
+
+ if (mode & QIODevice::WriteOnly) {
+ if (format.sampleFormat() != QAudioFormat::Int16)
+ return false; // data format is not supported
+ 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";
+ }
+ 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;
+ if (bps == 24)
+ return dataSize*2/3;
+ 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;
+
+ if (bps == 24) {
+ // 24 bit WAV, read in as 16 bit
+ qint64 l = 0;
+ while (l < maxlen - 1) {
+ char tmp[3];
+ device->read(tmp, 3);
+ if (byteSwap)
+ qSwap(tmp[0], tmp[2]);
+#if Q_BYTE_ORDER == Q_BIG_ENDIAN
+ data[0] = tmp[0];
+ data[1] = tmp[1];
+#else
+ data[0] = tmp[1];
+ data[1] = tmp[2];
+#endif
+ data += 2;
+ l += 2;
+ }
+ return l;
+ }
+
+ qint64 nSamples = maxlen / bytesPerSample;
+ maxlen = nSamples * bytesPerSample;
+ int read = device->read(data, maxlen);
+
+ if (!byteSwap || format.bytesPerFrame() == 1)
+ return read;
+
+ nSamples = read / bytesPerSample;
+ switch (bytesPerSample) {
+ case 2:
+ bswap2(data, nSamples);
+ break;
+ case 4:
+ bswap4(data, nSamples);
+ break;
+ default:
+ Q_UNREACHABLE();
+ }
+ return read;
+
+}
+
+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
+ // only implemented for LITTLE ENDIAN
+ return false;
+#endif
+
+ CombinedHeader header;
+
+ memset(&header, 0, HeaderLength);
+
+ // RIFF header
+ 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);
+
+ // WAVE header
+ 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));
+
+ // DATA header
+ 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
+ // only implemented for LITTLE ENDIAN
+ return false;
+#endif
+
+ if (isSequential())
+ return false;
+
+ // seek to RIFF header size, see header.riff.descriptor.size above
+ if (!device->seek(4)) {
+ qDebug() << "can't seek";
+ return false;
+ }
+
+ quint32 length = dataSize + HeaderLength - 8;
+ if (device->write(reinterpret_cast<const char *>(&length), 4) != 4)
+ return false;
+
+ // seek to DATA header size, see header.data.descriptor.size above
+ if (!device->seek(40))
+ return false;
+
+ return device->write(reinterpret_cast<const char *>(&dataSize), 4);
+}
+
+void QWaveDecoder::parsingFailed()
+{
+ Q_ASSERT(device);
+ disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
+ emit parsingError();
+}
+
+void QWaveDecoder::handleData()
+{
+ if (openMode() == QIODevice::WriteOnly)
+ return;
+
+ // As a special "state", if we have junk to skip, we do
+ if (junkToSkip > 0) {
+ discardBytes(junkToSkip); // this also updates junkToSkip
+
+ // If we couldn't skip all the junk, return
+ if (junkToSkip > 0) {
+ // We might have run out
+ if (device->atEnd())
+ parsingFailed();
+ return;
+ }
+ }
+
+ if (state == QWaveDecoder::InitialState) {
+ if (device->bytesAvailable() < qint64(sizeof(RIFFHeader)))
+ return;
+
+ RIFFHeader riff;
+ device->read(reinterpret_cast<char *>(&riff), sizeof(RIFFHeader));
+
+ // RIFF = little endian RIFF, RIFX = big endian RIFF
+ if (((qstrncmp(riff.descriptor.id, "RIFF", 4) != 0) && (qstrncmp(riff.descriptor.id, "RIFX", 4) != 0))
+ || qstrncmp(riff.type, "WAVE", 4) != 0) {
+ parsingFailed();
+ return;
+ }
+
+ state = QWaveDecoder::WaitingForFormatState;
+ bigEndian = (qstrncmp(riff.descriptor.id, "RIFX", 4) == 0);
+ byteSwap = (bigEndian != (QSysInfo::ByteOrder == QSysInfo::BigEndian));
+ }
+
+ if (state == QWaveDecoder::WaitingForFormatState) {
+ if (findChunk("fmt ")) {
+ chunk descriptor;
+ peekChunk(&descriptor);
+
+ quint32 rawChunkSize = descriptor.size + sizeof(chunk);
+ if (device->bytesAvailable() < qint64(rawChunkSize))
+ return;
+
+ WAVEHeader wave;
+ device->read(reinterpret_cast<char *>(&wave), sizeof(WAVEHeader));
+
+ if (rawChunkSize > sizeof(WAVEHeader))
+ discardBytes(rawChunkSize - sizeof(WAVEHeader));
+
+ // Swizzle this
+ if (bigEndian) {
+ wave.audioFormat = qFromBigEndian<quint16>(wave.audioFormat);
+ } else {
+ wave.audioFormat = qFromLittleEndian<quint16>(wave.audioFormat);
+ }
+
+ if (wave.audioFormat != 0 && wave.audioFormat != 1) {
+ // 32bit wave files have format == 0xFFFE (WAVE_FORMAT_EXTENSIBLE).
+ // but don't support them at the moment.
+ parsingFailed();
+ return;
+ }
+
+ int rate;
+ int channels;
+ if (bigEndian) {
+ bps = qFromBigEndian<quint16>(wave.bitsPerSample);
+ rate = qFromBigEndian<quint32>(wave.sampleRate);
+ channels = qFromBigEndian<quint16>(wave.numChannels);
+ } else {
+ bps = qFromLittleEndian<quint16>(wave.bitsPerSample);
+ rate = qFromLittleEndian<quint32>(wave.sampleRate);
+ channels = qFromLittleEndian<quint16>(wave.numChannels);
+ }
+
+ QAudioFormat::SampleFormat fmt = QAudioFormat::Unknown;
+ switch(bps) {
+ case 8:
+ fmt = QAudioFormat::UInt8;
+ break;
+ case 16:
+ fmt = QAudioFormat::Int16;
+ break;
+ case 24:
+ fmt = QAudioFormat::Int16;
+ break;
+ case 32:
+ fmt = QAudioFormat::Int32;
+ break;
+ }
+ if (fmt == QAudioFormat::Unknown || rate == 0 || channels == 0) {
+ parsingFailed();
+ return;
+ }
+
+ format.setSampleFormat(fmt);
+ format.setSampleRate(rate);
+ format.setChannelCount(channels);
+
+ state = QWaveDecoder::WaitingForDataState;
+ }
+ }
+
+ if (state == QWaveDecoder::WaitingForDataState) {
+ if (findChunk("data")) {
+ disconnect(device, &QIODevice::readyRead, this, &QWaveDecoder::handleData);
+
+ chunk descriptor;
+ device->read(reinterpret_cast<char *>(&descriptor), sizeof(chunk));
+ if (bigEndian)
+ descriptor.size = qFromBigEndian<quint32>(descriptor.size);
+ else
+ descriptor.size = qFromLittleEndian<quint32>(descriptor.size);
+
+ dataSize = descriptor.size; //means the data size from the data header, not the actual file size
+ if (!dataSize)
+ dataSize = device->size() - headerLength();
+
+ haveFormat = true;
+ connect(device, &QIODevice::readyRead, this, &QIODevice::readyRead);
+ emit formatKnown();
+
+ return;
+ }
+ }
+
+ // If we hit the end without finding data, it's a parsing error
+ if (device->atEnd()) {
+ parsingFailed();
+ }
+}
+
+bool QWaveDecoder::enoughDataAvailable()
+{
+ chunk descriptor;
+ if (!peekChunk(&descriptor, false))
+ return false;
+
+ // This is only called for the RIFF/RIFX header, before bigEndian is set,
+ // so we have to manually swizzle
+ if (qstrncmp(descriptor.id, "RIFX", 4) == 0)
+ descriptor.size = qFromBigEndian<quint32>(descriptor.size);
+ if (qstrncmp(descriptor.id, "RIFF", 4) == 0)
+ descriptor.size = qFromLittleEndian<quint32>(descriptor.size);
+
+ if (device->bytesAvailable() < qint64(sizeof(chunk) + descriptor.size))
+ return false;
+
+ return true;
+}
+
+bool QWaveDecoder::findChunk(const char *chunkId)
+{
+ chunk descriptor;
+
+ do {
+ if (!peekChunk(&descriptor))
+ return false;
+
+ if (qstrncmp(descriptor.id, chunkId, 4) == 0)
+ return true;
+
+ // A program reading a RIFF file can skip over any chunk whose chunk
+ // ID it doesn't recognize; it simply skips the number of bytes specified
+ // by ckSize plus the pad byte, if present. See Multimedia Programming
+ // Interface and Data Specifications 1.0. IBM / Microsoft. August 1991. pp. 10-11.
+ const quint32 sizeWithPad = descriptor.size + (descriptor.size & 1);
+
+ // It's possible that bytes->available() is less than the chunk size
+ // if it's corrupt.
+ junkToSkip = qint64(sizeof(chunk) + sizeWithPad);
+
+ // Skip the current amount
+ if (junkToSkip > 0)
+ discardBytes(junkToSkip);
+
+ // If we still have stuff left, just exit and try again later
+ // since we can't call peekChunk
+ if (junkToSkip > 0)
+ return false;
+
+ } while (device->bytesAvailable() > 0);
+
+ return false;
+}
+
+bool QWaveDecoder::peekChunk(chunk *pChunk, bool handleEndianness)
+{
+ if (device->bytesAvailable() < qint64(sizeof(chunk)))
+ return false;
+
+ if (!device->peek(reinterpret_cast<char *>(pChunk), sizeof(chunk)))
+ return false;
+
+ if (handleEndianness) {
+ if (bigEndian)
+ pChunk->size = qFromBigEndian<quint32>(pChunk->size);
+ else
+ pChunk->size = qFromLittleEndian<quint32>(pChunk->size);
+ }
+ return true;
+}
+
+void QWaveDecoder::discardBytes(qint64 numBytes)
+{
+ // Discards a number of bytes
+ // If the iodevice doesn't have this many bytes in it,
+ // remember how much more junk we have to skip.
+ if (device->isSequential()) {
+ QByteArray r = device->read(qMin(numBytes, qint64(16384))); // uggh, wasted memory, limit to a max of 16k
+ if (r.size() < numBytes)
+ junkToSkip = numBytes - r.size();
+ else
+ junkToSkip = 0;
+ } else {
+ quint64 origPos = device->pos();
+ device->seek(device->pos() + numBytes);
+ junkToSkip = origPos + numBytes - device->pos();
+ }
+}
+
+QT_END_NAMESPACE
+
+#include "moc_qwavedecoder.cpp"