mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-09 01:07:09 +02:00
Robust WAV parser.
Skips extraneous chunks, handles WAVE_FORMAT_EXTENSIBLE, supports arbitrary sample rates.
This commit is contained in:
parent
9831d874b5
commit
0f10975a59
1 changed files with 86 additions and 80 deletions
|
@ -195,8 +195,10 @@ struct RIFFHeader {
|
||||||
char type[4]; // "WAVE"
|
char type[4]; // "WAVE"
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WAVEHeader {
|
static const int WAVEFORMAT_PCM = 1;
|
||||||
chunk descriptor;
|
static const int WAVEFORMAT_EXTENSIBLE = 0xfffe;
|
||||||
|
|
||||||
|
struct WAVEFormat {
|
||||||
quint16 audioFormat; // Format type: 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM
|
quint16 audioFormat; // Format type: 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM
|
||||||
quint16 numChannels; // Number of channels: 1=mono, 2=stereo
|
quint16 numChannels; // Number of channels: 1=mono, 2=stereo
|
||||||
quint32 sampleRate;
|
quint32 sampleRate;
|
||||||
|
@ -205,90 +207,94 @@ struct WAVEHeader {
|
||||||
quint16 bitsPerSample;
|
quint16 bitsPerSample;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DATAHeader {
|
|
||||||
chunk descriptor;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CombinedHeader {
|
|
||||||
RIFFHeader riff;
|
|
||||||
WAVEHeader wave;
|
|
||||||
};
|
|
||||||
|
|
||||||
// returns wavfile sample rate, used for resampling
|
// returns wavfile sample rate, used for resampling
|
||||||
int Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
int Sound::interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray) {
|
||||||
|
|
||||||
CombinedHeader fileHeader;
|
|
||||||
|
|
||||||
// Create a data stream to analyze the data
|
// Create a data stream to analyze the data
|
||||||
QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly);
|
QDataStream waveStream(const_cast<QByteArray *>(&inputAudioByteArray), QIODevice::ReadOnly);
|
||||||
if (waveStream.readRawData(reinterpret_cast<char *>(&fileHeader), sizeof(CombinedHeader)) == sizeof(CombinedHeader)) {
|
|
||||||
|
|
||||||
if (strncmp(fileHeader.riff.descriptor.id, "RIFF", 4) == 0) {
|
// Read the "RIFF" chunk
|
||||||
waveStream.setByteOrder(QDataStream::LittleEndian);
|
RIFFHeader riff;
|
||||||
} else {
|
if (waveStream.readRawData((char*)&riff, sizeof(RIFFHeader)) != sizeof(RIFFHeader)) {
|
||||||
// descriptor.id == "RIFX" also signifies BigEndian file
|
qCDebug(audio) << "Not a valid WAVE file.";
|
||||||
// waveStream.setByteOrder(QDataStream::BigEndian);
|
|
||||||
qCDebug(audio) << "Currently not supporting big-endian audio files.";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strncmp(fileHeader.riff.type, "WAVE", 4) != 0
|
|
||||||
|| strncmp(fileHeader.wave.descriptor.id, "fmt", 3) != 0) {
|
|
||||||
qCDebug(audio) << "Not a WAVE Audio file.";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// added the endianess check as an extra level of security
|
|
||||||
|
|
||||||
if (qFromLittleEndian<quint16>(fileHeader.wave.audioFormat) != 1) {
|
|
||||||
qCDebug(audio) << "Currently not supporting non PCM audio files.";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) == 2) {
|
|
||||||
_isStereo = true;
|
|
||||||
} else if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) == 4) {
|
|
||||||
_isAmbisonic = true;
|
|
||||||
} else if (qFromLittleEndian<quint16>(fileHeader.wave.numChannels) != 1) {
|
|
||||||
qCDebug(audio) << "Currently not support audio files with other than 1/2/4 channels.";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (qFromLittleEndian<quint16>(fileHeader.wave.bitsPerSample) != 16) {
|
|
||||||
qCDebug(audio) << "Currently not supporting non 16bit audio files.";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip any extra data in the WAVE chunk
|
|
||||||
waveStream.skipRawData(fileHeader.wave.descriptor.size - (sizeof(WAVEHeader) - sizeof(chunk)));
|
|
||||||
|
|
||||||
// Read off remaining header information
|
|
||||||
DATAHeader dataHeader;
|
|
||||||
while (true) {
|
|
||||||
// Read chunks until the "data" chunk is found
|
|
||||||
if (waveStream.readRawData(reinterpret_cast<char *>(&dataHeader), sizeof(DATAHeader)) == sizeof(DATAHeader)) {
|
|
||||||
if (strncmp(dataHeader.descriptor.id, "data", 4) == 0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
waveStream.skipRawData(dataHeader.descriptor.size);
|
|
||||||
} else {
|
|
||||||
qCDebug(audio) << "Could not read wav audio data header.";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now pull out the data
|
|
||||||
quint32 outputAudioByteArraySize = qFromLittleEndian<quint32>(dataHeader.descriptor.size);
|
|
||||||
outputAudioByteArray.resize(outputAudioByteArraySize);
|
|
||||||
if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) {
|
|
||||||
qCDebug(audio) << "Error reading WAV file";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_duration = (float) (outputAudioByteArraySize / (fileHeader.wave.sampleRate * fileHeader.wave.numChannels * fileHeader.wave.bitsPerSample / 8.0f));
|
|
||||||
return fileHeader.wave.sampleRate;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qCDebug(audio) << "Could not read wav audio file header.";
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the "RIFF" chunk
|
||||||
|
if (strncmp(riff.descriptor.id, "RIFF", 4) == 0) {
|
||||||
|
waveStream.setByteOrder(QDataStream::LittleEndian);
|
||||||
|
} else {
|
||||||
|
qCDebug(audio) << "Currently not supporting big-endian audio files.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (strncmp(riff.type, "WAVE", 4) != 0) {
|
||||||
|
qCDebug(audio) << "Not a valid WAVE file.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read chunks until the "fmt " chunk is found
|
||||||
|
chunk fmt;
|
||||||
|
while (true) {
|
||||||
|
if (waveStream.readRawData((char*)&fmt, sizeof(chunk)) != sizeof(chunk)) {
|
||||||
|
qCDebug(audio) << "Not a valid WAVE file.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (strncmp(fmt.id, "fmt ", 4) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
waveStream.skipRawData(qFromLittleEndian<quint32>(fmt.size)); // next chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the "fmt " chunk
|
||||||
|
WAVEFormat wave;
|
||||||
|
if (waveStream.readRawData((char*)&wave, sizeof(WAVEFormat)) != sizeof(WAVEFormat)) {
|
||||||
|
qCDebug(audio) << "Not a valid WAVE file.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse the "fmt " chunk
|
||||||
|
if (qFromLittleEndian<quint16>(wave.audioFormat) != WAVEFORMAT_PCM &&
|
||||||
|
qFromLittleEndian<quint16>(wave.audioFormat) != WAVEFORMAT_EXTENSIBLE) {
|
||||||
|
qCDebug(audio) << "Currently not supporting non PCM audio files.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (qFromLittleEndian<quint16>(wave.numChannels) == 2) {
|
||||||
|
_isStereo = true;
|
||||||
|
} else if (qFromLittleEndian<quint16>(wave.numChannels) == 4) {
|
||||||
|
_isAmbisonic = true;
|
||||||
|
} else if (qFromLittleEndian<quint16>(wave.numChannels) != 1) {
|
||||||
|
qCDebug(audio) << "Currently not supporting audio files with other than 1/2/4 channels.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (qFromLittleEndian<quint16>(wave.bitsPerSample) != 16) {
|
||||||
|
qCDebug(audio) << "Currently not supporting non 16bit audio files.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip any extra data in the "fmt " chunk
|
||||||
|
waveStream.skipRawData(qFromLittleEndian<quint32>(fmt.size) - sizeof(WAVEFormat));
|
||||||
|
|
||||||
|
// Read chunks until the "data" chunk is found
|
||||||
|
chunk data;
|
||||||
|
while (true) {
|
||||||
|
if (waveStream.readRawData((char*)&data, sizeof(chunk)) != sizeof(chunk)) {
|
||||||
|
qCDebug(audio) << "Not a valid WAVE file.";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (strncmp(data.id, "data", 4) == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
waveStream.skipRawData(qFromLittleEndian<quint32>(data.size)); // next chunk
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the "data" chunk
|
||||||
|
quint32 outputAudioByteArraySize = qFromLittleEndian<quint32>(data.size);
|
||||||
|
outputAudioByteArray.resize(outputAudioByteArraySize);
|
||||||
|
if (waveStream.readRawData(outputAudioByteArray.data(), outputAudioByteArraySize) != (int)outputAudioByteArraySize) {
|
||||||
|
qCDebug(audio) << "Error reading WAV file";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_duration = (float)(outputAudioByteArraySize / (wave.sampleRate * wave.numChannels * wave.bitsPerSample / 8.0f));
|
||||||
|
return wave.sampleRate;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue