From e331dc529d671f0e0780b754a896d10ba61865a7 Mon Sep 17 00:00:00 2001 From: gaitat Date: Fri, 21 Feb 2014 19:26:19 -0500 Subject: [PATCH] Worklist Job #19499 Add WAV file support to Javascript --- libraries/audio/src/Sound.cpp | 160 +++++++++++++++++++++++++++++++++- libraries/audio/src/Sound.h | 5 ++ 2 files changed, 164 insertions(+), 1 deletion(-) diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index a918907ab0..6dc42379cf 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include "Sound.h" @@ -24,6 +25,7 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) : connect(manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*))); + qDebug() << "Requesting file" << sampleURL.fileName(); manager->get(QNetworkRequest(sampleURL)); } @@ -31,6 +33,36 @@ void Sound::replyFinished(QNetworkReply* reply) { // replace our byte array with the downloaded data QByteArray rawAudioByteArray = reply->readAll(); + // foreach(QByteArray b, reply->rawHeaderList()) + // qDebug() << b.constData() << ": " << reply->rawHeader(b).constData(); + + if (reply->hasRawHeader("Content-Type")) { + + QByteArray headerContentType = reply->rawHeader("Content-Type"); + + // RAW audio file encountered + if (headerContentType == "application/octet-stream") { + downSample(rawAudioByteArray); + } + + // WAV audio file encountered + else if (headerContentType == "audio/x-wav" + || headerContentType == "audio/wav" + || headerContentType == "audio/wave") { + + QByteArray outputAudioByteArray; + + interpretAsWav(rawAudioByteArray, outputAudioByteArray); + downSample(outputAudioByteArray); + } else { + qDebug() << "Unknown audio file 'Content-Type'."; + } + } else { + qDebug() << "Network reply without 'Content-Type'."; + } +} + +void Sound::downSample(const QByteArray& rawAudioByteArray) { // assume that this was a RAW file and is now an array of samples that are // signed, 16-bit, 48Khz, mono @@ -50,4 +82,130 @@ void Sound::replyFinished(QNetworkReply* reply) { destinationSamples[(i - 1) / 2] = (sourceSamples[i - 1] / 4) + (sourceSamples[i] / 2) + (sourceSamples[i + 1] / 4); } } -} \ No newline at end of file +} + +// +// Format description from https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ +// +// The header for a WAV file looks like this: +// Positions Sample Value Description +// 00-03 "RIFF" Marks the file as a riff file. Characters are each 1 byte long. +// 04-07 File size (int) Size of the overall file - 8 bytes, in bytes (32-bit integer). +// 08-11 "WAVE" File Type Header. For our purposes, it always equals "WAVE". +// 12-15 "fmt " Format chunk marker. +// 16-19 16 Length of format data as listed above +// 20-21 1 Type of format: (1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM) - 2 byte integer +// 22-23 2 Number of Channels - 2 byte integer +// 24-27 44100 Sample Rate - 32 byte integer. Sample Rate = Number of Samples per second, or Hertz. +// 28-31 176400 (Sample Rate * BitsPerSample * Channels) / 8. +// 32-33 4 (BitsPerSample * Channels) / 8 - 8 bit mono2 - 8 bit stereo/16 bit mono4 - 16 bit stereo +// 34-35 16 Bits per sample +// 36-39 "data" Chunk header. Marks the beginning of the data section. +// 40-43 File size (int) Size of the data section. +// 44-?? Actual sound data +// Sample values are given above for a 16-bit stereo source. +// + +struct chunk +{ + char id[4]; + quint32 size; +}; + +struct RIFFHeader +{ + chunk descriptor; // "RIFF" + char type[4]; // "WAVE" +}; + +struct WAVEHeader +{ + chunk descriptor; + quint16 audioFormat; // Format type: 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM + quint16 numChannels; // Number of channels: 1=mono, 2=stereo + quint32 sampleRate; + quint32 byteRate; // Sample rate * Number of Channels * Bits per sample / 8 + quint16 blockAlign; // (Number of Channels * Bits per sample) / 8.1 + quint16 bitsPerSample; +}; + +struct DATAHeader +{ + chunk descriptor; +}; + +struct CombinedHeader +{ + RIFFHeader riff; + WAVEHeader wave; +}; + +void Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) { + + CombinedHeader fileHeader; + + // Create a data stream to analyze the data + QDataStream waveStream(const_cast(&inputAudioByteArray), QIODevice::ReadOnly); + if (waveStream.readRawData(reinterpret_cast(&fileHeader), sizeof(CombinedHeader)) == sizeof(CombinedHeader)) { + + if (strncmp(fileHeader.riff.descriptor.id, "RIFF", 4) == 0) { + waveStream.setByteOrder(QDataStream::LittleEndian); + } else { + // descriptor.id == "RIFX" also signifies BigEndian file + // waveStream.setByteOrder(QDataStream::BigEndian); + qDebug() << "Currently not supporting big-endian audio files."; + return; + } + + if (strncmp(fileHeader.riff.type, "WAVE", 4) != 0 + || strncmp(fileHeader.wave.descriptor.id, "fmt", 3) != 0) { + qDebug() << "Not a WAVE Audio file."; + return; + } + + // added the endianess check as an extra level of security + + if (qFromLittleEndian(fileHeader.wave.audioFormat) != 1) { + qDebug() << "Currently not supporting non PCM audio files."; + return; + } + if (qFromLittleEndian(fileHeader.wave.numChannels) != 1) { + qDebug() << "Currently not supporting stereo audio files."; + return; + } + if (qFromLittleEndian(fileHeader.wave.bitsPerSample) != 16) { + qDebug() << "Currently not supporting non 16bit audio files."; + return; + } + if (qFromLittleEndian(fileHeader.wave.sampleRate) != 48000) { + qDebug() << "Currently not supporting non 48KHz audio files."; + return; + } + + // Read off remaining header information + DATAHeader dataHeader; + if (waveStream.readRawData(reinterpret_cast(&dataHeader), sizeof(DATAHeader)) == sizeof(DATAHeader)) { + if (strncmp(dataHeader.descriptor.id, "data", 4) != 0) { + qDebug() << "Invalid wav audio data header."; + return; + } + } else { + qDebug() << "Could not read wav audio data header."; + return; + } + + if (qFromLittleEndian(fileHeader.riff.descriptor.size) != qFromLittleEndian(dataHeader.descriptor.size) + 36) { + qDebug() << "Did not read audio file chank headers correctly."; + return; + } + + // Now pull out the data + quint32 outputAudioByteArraySize = qFromLittleEndian(dataHeader.descriptor.size); + outputAudioByteArray.resize(outputAudioByteArraySize); + waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize); + + } else { + qDebug() << "Could not read wav audio file header."; + return; + } +} diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 82b59c8b49..b74b3f40a7 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -19,8 +19,13 @@ public: Sound(const QUrl& sampleURL, QObject* parent = 0); const QByteArray& getByteArray() { return _byteArray; } + private: QByteArray _byteArray; + + void downSample(const QByteArray& rawAudioByteArray); + void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); + private slots: void replyFinished(QNetworkReply* reply); };