Skip to content

Commit

Permalink
#5345: WAV file length calculation is working now
Browse files Browse the repository at this point in the history
  • Loading branch information
codereader committed Oct 4, 2020
1 parent b77ff04 commit 641b507
Show file tree
Hide file tree
Showing 2 changed files with 154 additions and 90 deletions.
15 changes: 11 additions & 4 deletions plugins/sound/SoundManager.cpp
Expand Up @@ -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;
Expand Down
229 changes: 143 additions & 86 deletions plugins/sound/WavFileLoader.h
Expand Up @@ -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<byte*>(&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<float>(numSamplesPerChannel) / info.freq;
}

/**
Expand All @@ -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<byte*>(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<byte*>(&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<byte*>(&size), 4);
ALuint bufferNum = 0;
alGenBuffers(1, &bufferNum);

// check file format
stream.read(reinterpret_cast<byte*>(magic), 4);
if (std::string(magic) != "WAVE") {
throw std::runtime_error("Wrong wav file format");
}
std::vector<byte> data(remainingSize);
stream.read(data.data(), remainingSize);

// check 'fmt ' sub chunk (1)
stream.read(reinterpret_cast<byte*>(magic), 4);
if (std::string(magic) != "fmt ") {
throw std::runtime_error("No 'fmt ' subchunk.");
}
alBufferData(bufferNum, info.getAlFormat(), data.data(), static_cast<ALsizei>(remainingSize), info.freq);

// read (1)'s size
unsigned int subChunk1Size(0);
stream.read(reinterpret_cast<byte*>(&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<byte*>(&audioFormat), 2);
// check 'data' sub chunk (2)
stream.read(reinterpret_cast<byte*>(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<byte*>(&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<byte*>(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<byte*>(info.magic), 4);

// read frequency (sample rate)
unsigned int freq = 0;
stream.read(reinterpret_cast<byte*>(&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<byte*>(&info.size), sizeof(info.size));

// read bits per sample
unsigned short bps = 0;
stream.read(reinterpret_cast<byte*>(&bps), 2);
// check file format
stream.read(reinterpret_cast<byte*>(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<byte*>(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<byte*>(&subChunk1Size), 4);

if (subChunk1Size < 16)
{
format = bps == 8 ? AL_FORMAT_STEREO16 : AL_FORMAT_STEREO16;
}

// check 'data' sub chunk (2)
stream.read(reinterpret_cast<byte*>(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<byte*>(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<byte*>(&remainingSize), 4);
throw std::runtime_error("'fmt ' chunk too small.");
}

ALuint bufferNum = 0;
alGenBuffers(1, &bufferNum);
// check PCM audio format
stream.read(reinterpret_cast<byte*>(&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<ALsizei>(remainingSize), freq);
// read number of channels
stream.read(reinterpret_cast<byte*>(&info.channels), sizeof(info.channels));

delete[] buffer;
// read frequency (sample rate)
stream.read(reinterpret_cast<byte*>(&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<byte*>(&info.bps), sizeof(info.bps));
}
};

} // namespace sound

0 comments on commit 641b507

Please sign in to comment.