From 641b507423cb542ddb8b3cb32bd4dd8b3416e1c7 Mon Sep 17 00:00:00 2001 From: codereader Date: Sun, 4 Oct 2020 13:52:04 +0200 Subject: [PATCH] #5345: WAV file length calculation is working now --- plugins/sound/SoundManager.cpp | 15 ++- plugins/sound/WavFileLoader.h | 229 ++++++++++++++++++++------------- 2 files changed, 154 insertions(+), 90 deletions(-) diff --git a/plugins/sound/SoundManager.cpp b/plugins/sound/SoundManager.cpp index c6b77e89e6..dceae2b5e3 100644 --- a/plugins/sound/SoundManager.cpp +++ b/plugins/sound/SoundManager.cpp @@ -188,13 +188,20 @@ float SoundManager::getSoundFileDuration(const std::string& vfsPath) auto extension = string::to_lower_copy(os::getExtension(file->getName())); - if (extension == "wav") + try { - return WavFileLoader::GetDuration(file->getInputStream()); + if (extension == "wav") + { + return WavFileLoader::GetDuration(file->getInputStream()); + } + else if (extension == "ogg") + { + return 23.45f; + } } - else if (extension == "ogg") + catch (const std::runtime_error& ex) { - return 23.45f; + rError() << "Error determining sound file duration " << ex.what() << std::endl; } return 0.0f; diff --git a/plugins/sound/WavFileLoader.h b/plugins/sound/WavFileLoader.h index f9c0c2c169..91a0021beb 100644 --- a/plugins/sound/WavFileLoader.h +++ b/plugins/sound/WavFileLoader.h @@ -21,10 +21,62 @@ namespace sound { */ class WavFileLoader { +private: + struct FileInfo + { + char magic[5]; + unsigned int size; + char fileFormat[5]; + unsigned short audioFormat; + unsigned short channels; + unsigned int freq; + unsigned short bps; // bits per sample + + FileInfo() + { + magic[4] = '\0'; + fileFormat[4] = '\0'; + audioFormat = 0; + } + + ALenum getAlFormat() const + { + if (channels == 1) + { + return bps == 8 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16; + } + else + { + return bps == 8 ? AL_FORMAT_STEREO16 : AL_FORMAT_STEREO16; + } + } + }; + + typedef StreamBase::byte_type byte; + public: + /** + * greebo: Loads a WAV file from a stream into OpenAL, + * returns the openAL buffer handle. + * + * @throws: std::runtime_error if an error occurs. + */ static float GetDuration(InputStream& stream) { - return -1.0f; + FileInfo info; + ParseFileInfo(stream, info); + + SkipToRemainingData(stream); + + // The next four bytes are the remaining size of the file + unsigned int remainingSize = 0; + stream.read(reinterpret_cast(&remainingSize), sizeof(remainingSize)); + + // Calculate how many samples we have in the payload, then calculate the duration + auto numSamples = remainingSize / (info.bps >> 3); + auto numSamplesPerChannel = numSamples / info.channels; + + return static_cast(numSamplesPerChannel) / info.freq; } /** @@ -35,112 +87,117 @@ class WavFileLoader */ static ALuint LoadFromStream(InputStream& stream) { - // buffers - char magic[5]; - magic[4] = '\0'; - typedef StreamBase::byte_type byte; - - byte temp[256]; - - int format = 0; + FileInfo info; + ParseFileInfo(stream, info); - // check magic - stream.read(reinterpret_cast(magic), 4); + SkipToRemainingData(stream); - if (std::string(magic) != "RIFF") { - throw std::runtime_error("No wav file"); - } + // The next four bytes are the remaining size of the file + unsigned int remainingSize = 0; + stream.read(reinterpret_cast(&remainingSize), sizeof(remainingSize)); - // The next 4 bytes are the file size, we can skip this since we get the size from the DataStream - unsigned int size; - stream.read(reinterpret_cast(&size), 4); + ALuint bufferNum = 0; + alGenBuffers(1, &bufferNum); - // check file format - stream.read(reinterpret_cast(magic), 4); - if (std::string(magic) != "WAVE") { - throw std::runtime_error("Wrong wav file format"); - } + std::vector data(remainingSize); + stream.read(data.data(), remainingSize); - // check 'fmt ' sub chunk (1) - stream.read(reinterpret_cast(magic), 4); - if (std::string(magic) != "fmt ") { - throw std::runtime_error("No 'fmt ' subchunk."); - } + alBufferData(bufferNum, info.getAlFormat(), data.data(), static_cast(remainingSize), info.freq); - // read (1)'s size - unsigned int subChunk1Size(0); - stream.read(reinterpret_cast(&subChunk1Size), 4); + return bufferNum; + } - if (subChunk1Size < 16) { - throw std::runtime_error("'fmt ' chunk too small."); - } +private: + // Assuming that the FMT chunk has been parsed, this seeks forward to the + // beginning of the sound data + static void SkipToRemainingData(InputStream& stream) + { + char buffer[5]; + buffer[4] = '\0'; - // check PCM audio format - unsigned short audioFormat(0); - stream.read(reinterpret_cast(&audioFormat), 2); + // check 'data' sub chunk (2) + stream.read(reinterpret_cast(buffer), 4); - if (audioFormat != 1) { - throw std::runtime_error("Audio format is not PCM."); - } + if (std::string(buffer) != "data" && std::string(buffer) != "fact") + { + throw std::runtime_error("No 'data' subchunk."); + } - // read number of channels - unsigned short channels(0); - stream.read(reinterpret_cast(&channels), 2); + // fact is an option section we don't need to worry about + if (std::string(buffer) == "fact") + { + byte temp[8]; + stream.read(temp, 8); + + // Now we should hit the data chunk + stream.read(reinterpret_cast(buffer), 4); + if (std::string(buffer) != "data") + { + throw std::runtime_error("No 'data' subchunk."); + } + } + } + + // Reads the file header, leaves the stream right after the end of the first chunk + static void ParseFileInfo(InputStream& stream, FileInfo& info) + { + // check magic + stream.read(reinterpret_cast(info.magic), 4); - // read frequency (sample rate) - unsigned int freq = 0; - stream.read(reinterpret_cast(&freq), 4); + if (std::string(info.magic) != "RIFF") + { + throw std::runtime_error("No wav file"); + } - // skip 6 bytes (Byte rate (4), Block align (2)) - stream.read(temp, 6); + // The next 4 bytes are the file size, we can skip this since we get the size from the DataStream + stream.read(reinterpret_cast(&info.size), sizeof(info.size)); - // read bits per sample - unsigned short bps = 0; - stream.read(reinterpret_cast(&bps), 2); + // check file format + stream.read(reinterpret_cast(info.fileFormat), 4); - if (channels == 1) + if (std::string(info.fileFormat) != "WAVE") { - format = bps == 8 ? AL_FORMAT_MONO8 : AL_FORMAT_MONO16; - } - else + throw std::runtime_error("Wrong wav file format"); + } + + // check 'fmt ' sub chunk (1) + char buffer[5]; + buffer[4] = '\0'; + stream.read(reinterpret_cast(buffer), 4); + if (std::string(buffer) != "fmt ") { + throw std::runtime_error("No 'fmt ' subchunk."); + } + + // read (1)'s size + unsigned int subChunk1Size(0); + stream.read(reinterpret_cast(&subChunk1Size), 4); + + if (subChunk1Size < 16) { - format = bps == 8 ? AL_FORMAT_STEREO16 : AL_FORMAT_STEREO16; - } - - // check 'data' sub chunk (2) - stream.read(reinterpret_cast(magic), 4); - if (std::string(magic) != "data" && std::string(magic) != "fact") { - throw std::runtime_error("No 'data' subchunk."); - } - - // fact is an option section we don't need to worry about - if (std::string(magic) == "fact") - { - stream.read(temp, 8); - - // Now we should hit the data chunk - stream.read(reinterpret_cast(magic), 4); - if (std::string(magic) != "data") { - throw std::runtime_error("No 'data' subchunk."); - } - } - - // The next four bytes are the size remaing of the file - unsigned int remainingSize = 0; - stream.read(reinterpret_cast(&remainingSize), 4); + throw std::runtime_error("'fmt ' chunk too small."); + } - ALuint bufferNum = 0; - alGenBuffers(1, &bufferNum); + // check PCM audio format + stream.read(reinterpret_cast(&info.audioFormat), sizeof(info.audioFormat)); - byte* buffer = new byte[remainingSize]; - stream.read(buffer, remainingSize); + if (info.audioFormat != 1) + { + throw std::runtime_error("Audio format is not PCM."); + } - alBufferData(bufferNum, format, buffer, static_cast(remainingSize), freq); + // read number of channels + stream.read(reinterpret_cast(&info.channels), sizeof(info.channels)); - delete[] buffer; + // read frequency (sample rate) + stream.read(reinterpret_cast(&info.freq), sizeof(info.freq)); - return bufferNum; - } + // skip 6 bytes (Byte rate (4), Block align (2)) + byte temp[6]; + stream.read(temp, 6); + + // read bits per sample + stream.read(reinterpret_cast(&info.bps), sizeof(info.bps)); + } }; } // namespace sound