|
|
|
|
@ -31,6 +31,12 @@
|
|
|
|
|
|
|
|
|
|
#include "MagnumPlugins/WavAudioImporter/WavHeader.h" |
|
|
|
|
|
|
|
|
|
#define WAVE_FORMAT_PCM 0x0001 |
|
|
|
|
#define WAVE_FORMAT_IEEE_FLOAT 0x0003 |
|
|
|
|
#define WAVE_FORMAT_ALAW 0x0006 |
|
|
|
|
#define WAVE_FORMAT_MULAW 0x0007 |
|
|
|
|
#define WAVE_FORMAT_EXTENSIBLE 0xFFFE |
|
|
|
|
|
|
|
|
|
namespace Magnum { namespace Audio { |
|
|
|
|
|
|
|
|
|
WavImporter::WavImporter() = default; |
|
|
|
|
@ -43,74 +49,173 @@ bool WavImporter::doIsOpened() const { return _data; }
|
|
|
|
|
|
|
|
|
|
void WavImporter::doOpenData(Containers::ArrayView<const char> data) { |
|
|
|
|
/* Check file size */ |
|
|
|
|
if(data.size() < sizeof(WavHeader)) { |
|
|
|
|
if(data.size() < sizeof(WavHeaderChunk) + sizeof(WavFormatChunk) + sizeof(RiffChunk)) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file is too short:" << data.size() << "bytes"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Get header contents and fix endianness */ |
|
|
|
|
WavHeader header(*reinterpret_cast<const WavHeader*>(data.begin())); |
|
|
|
|
Utility::Endianness::littleEndianInPlace(header.chunkSize, |
|
|
|
|
header.subChunk1Size, header.audioFormat, header.numChannels, |
|
|
|
|
header.sampleRate, header.byteRate, header.blockAlign, |
|
|
|
|
header.bitsPerSample, header.subChunk2Size); |
|
|
|
|
|
|
|
|
|
/* Check file signature */ |
|
|
|
|
if(std::strncmp(header.chunkId, "RIFF", 4) != 0 || |
|
|
|
|
std::strncmp(header.format, "WAVE", 4) != 0 || |
|
|
|
|
std::strncmp(header.subChunk1Id, "fmt ", 4) != 0 || |
|
|
|
|
std::strncmp(header.subChunk2Id, "data", 4) != 0) { |
|
|
|
|
/* Get the RIFF/WAV header */ |
|
|
|
|
WavHeaderChunk header(*reinterpret_cast<const WavHeaderChunk*>(data.begin())); |
|
|
|
|
|
|
|
|
|
/* Check RIFF/WAV file signature */ |
|
|
|
|
if(std::strncmp(header.chunk.chunkId, "RIFF", 4) != 0 || |
|
|
|
|
std::strncmp(header.format, "WAVE", 4) != 0) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file signature is invalid"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
Utility::Endianness::littleEndianInPlace(header.chunk.chunkSize); |
|
|
|
|
|
|
|
|
|
/* Check file size */ |
|
|
|
|
if(header.chunkSize + 8 != data.size()) { |
|
|
|
|
if(header.chunk.chunkSize < 36 || header.chunk.chunkSize + 8 != data.size()) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file has improper size, expected" |
|
|
|
|
<< header.chunkSize + 8 << "but got" << data.size(); |
|
|
|
|
<< header.chunk.chunkSize + 8 << "but got" << data.size(); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
const RiffChunk* dataChunk = nullptr; |
|
|
|
|
const WavFormatChunk* formatChunk = nullptr; |
|
|
|
|
UnsignedInt dataChunkSize = 0; |
|
|
|
|
|
|
|
|
|
const UnsignedInt headerSize = sizeof(WavHeaderChunk); |
|
|
|
|
UnsignedInt offset = 0; |
|
|
|
|
|
|
|
|
|
/* Skip any chunks that aren't the format or data chunk */ |
|
|
|
|
while(headerSize + offset <= header.chunk.chunkSize) { |
|
|
|
|
const RiffChunk* currChunk = reinterpret_cast<const RiffChunk*>(data.begin() + headerSize + offset); |
|
|
|
|
offset += Utility::Endianness::littleEndian(currChunk->chunkSize) + sizeof(RiffChunk); |
|
|
|
|
|
|
|
|
|
if(std::strncmp(currChunk->chunkId, "fmt ", 4) == 0) { |
|
|
|
|
if(formatChunk != nullptr) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file contains too many format chunks"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
formatChunk = reinterpret_cast<const WavFormatChunk*>(currChunk); |
|
|
|
|
|
|
|
|
|
} else if(std::strncmp(currChunk->chunkId, "data", 4) == 0) { |
|
|
|
|
if(dataChunk != nullptr) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file contains too many data chunks"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
dataChunk = currChunk; |
|
|
|
|
dataChunkSize = Utility::Endianness::littleEndian(currChunk->chunkSize); |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Make sure we actually got a format chunk */ |
|
|
|
|
if(formatChunk == nullptr) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file contains no format chunk"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Make sure we actually got a data chunk */ |
|
|
|
|
if(dataChunk == nullptr) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file contains no data chunk"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Fix endianness on Format chunk */ |
|
|
|
|
Utility::Endianness::littleEndianInPlace( |
|
|
|
|
formatChunk->chunk.chunkSize, formatChunk->audioFormat, formatChunk->numChannels, |
|
|
|
|
formatChunk->sampleRate, formatChunk->byteRate, formatChunk->blockAlign, |
|
|
|
|
formatChunk->bitsPerSample); |
|
|
|
|
|
|
|
|
|
/* Check PCM format */ |
|
|
|
|
if(header.audioFormat != 1) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported audio format" << header.audioFormat; |
|
|
|
|
if(formatChunk->audioFormat == WAVE_FORMAT_PCM) { |
|
|
|
|
/* Decide about format */ |
|
|
|
|
if(formatChunk->numChannels == 1 && formatChunk->bitsPerSample == 8) |
|
|
|
|
_format = Buffer::Format::Mono8; |
|
|
|
|
else if(formatChunk->numChannels == 1 && formatChunk->bitsPerSample == 16) |
|
|
|
|
_format = Buffer::Format::Mono16; |
|
|
|
|
else if(formatChunk->numChannels == 2 && formatChunk->bitsPerSample == 8) |
|
|
|
|
_format = Buffer::Format::Stereo8; |
|
|
|
|
else if(formatChunk->numChannels == 2 && formatChunk->bitsPerSample == 16) |
|
|
|
|
_format = Buffer::Format::Stereo16; |
|
|
|
|
else { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported channel count" |
|
|
|
|
<< formatChunk->numChannels << "with" << formatChunk->bitsPerSample |
|
|
|
|
<< "bits per sample"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
/* Check IEEE Float format */ |
|
|
|
|
} else if(formatChunk->audioFormat == WAVE_FORMAT_IEEE_FLOAT) { |
|
|
|
|
if(formatChunk->numChannels == 1 && formatChunk->bitsPerSample == 32) |
|
|
|
|
_format = Buffer::Format::MonoFloat; |
|
|
|
|
else if(formatChunk->numChannels == 2 && formatChunk->bitsPerSample == 32) |
|
|
|
|
_format = Buffer::Format::StereoFloat; |
|
|
|
|
else if(formatChunk->numChannels == 1 && formatChunk->bitsPerSample == 64) |
|
|
|
|
_format = Buffer::Format::MonoDouble; |
|
|
|
|
else if(formatChunk->numChannels == 2 && formatChunk->bitsPerSample == 64) |
|
|
|
|
_format = Buffer::Format::StereoDouble; |
|
|
|
|
else { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported channel count" |
|
|
|
|
<< formatChunk->numChannels << "with" << formatChunk->bitsPerSample |
|
|
|
|
<< "bits per sample"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
/* Check ALAW format */ |
|
|
|
|
} else if(formatChunk->audioFormat == WAVE_FORMAT_ALAW) { |
|
|
|
|
if(formatChunk->numChannels == 1) |
|
|
|
|
_format = Buffer::Format::MonoALaw; |
|
|
|
|
else if(formatChunk->numChannels == 2) |
|
|
|
|
_format = Buffer::Format::StereoALaw; |
|
|
|
|
else { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported channel count" |
|
|
|
|
<< formatChunk->numChannels << "with" << formatChunk->bitsPerSample |
|
|
|
|
<< "bits per sample"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
/* Check MULAW format */ |
|
|
|
|
} else if(formatChunk->audioFormat == WAVE_FORMAT_MULAW) { |
|
|
|
|
if(formatChunk->numChannels == 1) |
|
|
|
|
_format = Buffer::Format::MonoMuLaw; |
|
|
|
|
else if(formatChunk->numChannels == 2) |
|
|
|
|
_format = Buffer::Format::StereoMuLaw; |
|
|
|
|
else { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported channel count" |
|
|
|
|
<< formatChunk->numChannels << "with" << formatChunk->bitsPerSample |
|
|
|
|
<< "bits per sample"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
/* We do not currently support EXTENSIBLE formats */ |
|
|
|
|
} else if(formatChunk->audioFormat == WAVE_FORMAT_EXTENSIBLE) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported audio format: extensible not implememented" << formatChunk->audioFormat; |
|
|
|
|
return; |
|
|
|
|
/* Unknown format */ |
|
|
|
|
} else { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported audio format" << formatChunk->audioFormat; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Verify more things */ |
|
|
|
|
if(header.subChunk1Size != 16 || |
|
|
|
|
header.subChunk2Size + 44 != data.size() || |
|
|
|
|
header.blockAlign != header.numChannels*header.bitsPerSample/8 || |
|
|
|
|
header.byteRate != header.sampleRate*header.blockAlign) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file is corrupted"; |
|
|
|
|
/* Size sanity checks */ |
|
|
|
|
if(headerSize + offset > data.size()) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): file size doesn't match computed size"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Decide about format */ |
|
|
|
|
if(header.numChannels == 1 && header.bitsPerSample == 8) |
|
|
|
|
_format = Buffer::Format::Mono8; |
|
|
|
|
else if(header.numChannels == 1 && header.bitsPerSample == 16) |
|
|
|
|
_format = Buffer::Format::Mono16; |
|
|
|
|
else if(header.numChannels == 2 && header.bitsPerSample == 8) |
|
|
|
|
_format = Buffer::Format::Stereo8; |
|
|
|
|
else if(header.numChannels == 2 && header.bitsPerSample == 16) |
|
|
|
|
_format = Buffer::Format::Stereo16; |
|
|
|
|
else { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported channel count" |
|
|
|
|
<< header.numChannels << "with" << header.bitsPerSample |
|
|
|
|
<< "bits per sample"; |
|
|
|
|
/* Format sanity checks */ |
|
|
|
|
if(formatChunk->blockAlign != formatChunk->numChannels * formatChunk->bitsPerSample / 8 || |
|
|
|
|
formatChunk->byteRate != formatChunk->sampleRate * formatChunk->blockAlign) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file is corrupted"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Save frequency */ |
|
|
|
|
_frequency = header.sampleRate; |
|
|
|
|
_frequency = formatChunk->sampleRate; |
|
|
|
|
|
|
|
|
|
/** @todo Convert the data from little endian too */ |
|
|
|
|
CORRADE_INTERNAL_ASSERT(!Utility::Endianness::isBigEndian()); |
|
|
|
|
|
|
|
|
|
/* Copy the data */ |
|
|
|
|
_data = Containers::Array<char>(header.subChunk2Size); |
|
|
|
|
std::copy(data.begin()+sizeof(WavHeader), data.end(), _data.begin()); |
|
|
|
|
const char* dataChunkPtr = reinterpret_cast<const char*>(dataChunk + 1); |
|
|
|
|
_data = Containers::Array<char>(dataChunkSize); |
|
|
|
|
std::copy(dataChunkPtr, dataChunkPtr+dataChunkSize, _data.begin()); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|