|
|
|
|
@ -43,74 +43,161 @@ 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(WavDataChunk)) { |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Get the WAV format header */ |
|
|
|
|
WavFormatChunk formatChunk(*reinterpret_cast<const WavFormatChunk*>(data.begin() + sizeof(WavHeaderChunk))); |
|
|
|
|
|
|
|
|
|
/* Check is the format header is directly below WAV header */ |
|
|
|
|
if(std::strncmp(formatChunk.chunk.chunkId, "fmt ", 4) != 0) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file signature is invalid"; |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else if(formatChunk.audioFormat == WAVE_FORMAT_EXTENSIBLE) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): unsupported audio format: extensible not implememented" << formatChunk.audioFormat; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
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) { |
|
|
|
|
/* 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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* 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"; |
|
|
|
|
const RiffChunk* dataChunk = nullptr; |
|
|
|
|
UnsignedInt dataChunkSize = 0; |
|
|
|
|
|
|
|
|
|
const UnsignedInt headerSize = sizeof(WavHeaderChunk) + sizeof(RiffChunk) + formatChunk.chunk.chunkSize; |
|
|
|
|
UnsignedInt offset = 0; |
|
|
|
|
|
|
|
|
|
/* Skip any chunks that aren't the 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, "data", 4) == 0) { |
|
|
|
|
dataChunk = currChunk; |
|
|
|
|
dataChunkSize = Utility::Endianness::littleEndian(currChunk->chunkSize); |
|
|
|
|
|
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Make sure we actually got a data chunk */ |
|
|
|
|
if(dataChunk == nullptr) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): the file contains no data chunk"; |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/* Size sanity checks */ |
|
|
|
|
if(headerSize + offset > data.size()) { |
|
|
|
|
Error() << "Audio::WavImporter::openData(): file size doesn't match computed size"; |
|
|
|
|
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); |
|
|
|
|
_data = Containers::Array<char>(dataChunkSize); |
|
|
|
|
std::copy(dataChunkPtr, dataChunkPtr+dataChunkSize, _data.begin()); |
|
|
|
|
return; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|