I am trying to load .WAV files to be played with OpenAL. I am following an example I found on the internet, but it is acting strangely. Here is the code:
struct RIFF_Header {
char chunkID[4];
long chunkSize;//size not including chunkSize or chunkID
char format[4];
};
/*
* Struct to hold fmt subchunk data for WAVE files.
*/
struct WAVE_Format {
char subChunkID[4];
long subChunkSize;
short audioFormat;
short numChannels;
long sampleRate;
long byteRate;
short blockAlign;
short bitsPerSample;
};
/*
* Struct to hold the data of the wave file
*/
struct WAVE_Data {
char subChunkID[4]; //should contain the word data
long subChunk2Size; //Stores the size of the data block
};
bool loadWavFile(std::string filename, ALuint* buffer,
ALsizei* size, ALsizei* frequency,
ALenum* format) {
//Local Declarations
FILE* soundFile = NULL;
WAVE_Format wave_format;
RIFF_Header riff_header;
WAVE_Data wave_data;
unsigned char* data;
*size = wave_data.subChunk2Size;
*frequency = wave_format.sampleRate;
if (wave_format.numChannels == 1) {
if (wave_format.bitsPerSample == 8 )
*format = AL_FORMAT_MONO8;
else if (wave_format.bitsPerSample == 16)
*format = AL_FORMAT_MONO16;
} else if (wave_format.numChannels == 2) {
if (wave_format.bitsPerSample == 8 )
*format = AL_FORMAT_STEREO8;
else if (wave_format.bitsPerSample == 16)
*format = AL_FORMAT_STEREO16;
}
try {
soundFile = fopen(filename.c_str(), "rb");
if (!soundFile)
throw (filename);
// Read in the first chunk into the struct
fread(&riff_header, sizeof(RIFF_Header), 1, soundFile);
//check for RIFF and WAVE tag in memeory
if ((riff_header.chunkID[0] != 'R' ||
riff_header.chunkID[1] != 'I' ||
riff_header.chunkID[2] != 'F' ||
riff_header.chunkID[3] != 'F') ||
(riff_header.format[0] != 'W' ||
riff_header.format[1] != 'A' ||
riff_header.format[2] != 'V' ||
riff_header.format[3] != 'E'))
throw ("Invalid RIFF or WAVE Header");
//Read in the 2nd chunk for the wave info
fread(&wave_format, sizeof(WAVE_Format), 1, soundFile);
//check for fmt tag in memory
if (wave_format.subChunkID[0] != 'f' ||
wave_format.subChunkID[1] != 'm' ||
wave_format.subChunkID[2] != 't' ||
wave_format.subChunkID[3] != ' ')
throw ("Invalid Wave Format");
//check for extra parameters;
if (wave_format.subChunkSize > 16)
fseek(soundFile, sizeof(short), SEEK_CUR);
//Read in the the last byte of data before the sound file
fread(&wave_data, sizeof(WAVE_Data), 1, soundFile);
//check for data tag in memory
if (wave_data.subChunkID[0] != 'd' ||
wave_data.subChunkID[1] != 'a' ||
wave_data.subChunkID[2] != 't' ||
wave_data.subChunkID[3] != 'a')
throw ("Invalid data header");
//Allocate memory for data
data = new unsigned char[wave_data.subChunk2Size];
// Read in the sound data into the soundData variable
if (!fread(data, wave_data.subChunk2Size, 1, soundFile))
throw ("error loading WAVE data into struct!");
//Now we set the variables that we passed in with the
//data from the structs
*size = wave_data.subChunk2Size;
*frequency = wave_format.sampleRate;
//The format is worked out by looking at the number of
//channels and the bits per sample.
if (wave_format.numChannels == 1) {
if (wave_format.bitsPerSample == 8 )
*format = AL_FORMAT_MONO8;
else if (wave_format.bitsPerSample == 16)
*format = AL_FORMAT_MONO16;
} else if (wave_format.numChannels == 2) {
if (wave_format.bitsPerSample == 8 )
*format = AL_FORMAT_STEREO8;
else if (wave_format.bitsPerSample == 16)
*format = AL_FORMAT_STEREO16;
}
//create our openAL buffer and check for success
alGenBuffers(2, buffer);
if(alGetError() != AL_NO_ERROR) {
std::cerr << alGetError() << std::endl;
}
//now we put our data into the openAL buffer and
//check for success
alBufferData(*buffer, *format, (void*)data,
*size, *frequency);
if(alGetError() != AL_NO_ERROR) {
std::cerr << alGetError() << std::endl;
}
//clean up and return true if successful
fclose(soundFile);
delete data;
return true;
} catch(const char* error) {
//our catch statement for if we throw a string
std::cerr << error << " : trying to load "
<< filename << std::endl;
//clean up memory if wave loading fails
if (soundFile != NULL)
fclose(soundFile);
//return false to indicate the failure to load wave
delete data;
return false;
}
}
int main() {
ALuint buffer, source;
ALint state;
ALsizei size;
ALsizei frequency;
ALenum format;
ALCcontext *context;
ALCdevice *device;
device = alcOpenDevice(nullptr);
if (device == NULL)
{
cerr << "Error finding default Audio Output Device" << endl;
}
context = alcCreateContext(device,NULL);
alcMakeContextCurrent(context);
alGetError();
loadWavFile("test.wav", &buffer, &size, &frequency, &format);
alGenSources(1, &source);
alSourcei(source, AL_BUFFER, buffer);
// Play
alSourcePlay(source);
// Wait for the song to complete
do {
alGetSourcei(source, AL_SOURCE_STATE, &state);
} while (state == AL_PLAYING);
// Clean up sources and buffers
alDeleteSources(1, &source);
alDeleteBuffers(1, &buffer);
return 0;
}
I have several WAV files both around 50kb. They load and play just fine. However, when I try to load a whole song (yes, I verified the file was correctly formatted using VLC Media Player and MusicBee) it returns 'Invalid data header : trying to load test.wav', which is caused by this chunk right here:
if (wave_data.subChunkID[0] != 'd' ||
wave_data.subChunkID[1] != 'a' ||
wave_data.subChunkID[2] != 't' ||
wave_data.subChunkID[3] != 'a')
throw ("Invalid data header");
I suspect that it is something size-related that is throwing off that header, as it seems that things only under 1000kb work (haven't totally tested that, its hard to find perfectly sized sound files floating around on my computer and on the internet). That is only a guess though, I really haven't a clue what is going on. Help is appreciated!
I know this question is around for a long time but I found a tutorial and I tested it. It works. Try this:
//http://www.youtube.com/user/thecplusplusguy
//Playing 3D sound with OpenAL, and loading a wav file manually
#include <iostream>
#include <fstream>
#include <cstring>
#include <al.h>
#include <alc.h>
bool isBigEndian()
{
int a = 1;
return !((char*)&a)[0];
}
int convertToInt(char* buffer, int len)
{
int a = 0;
if (!isBigEndian())
for (int i = 0; i<len; i++)
((char*)&a)[i] = buffer[i];
else
for (int i = 0; i<len; i++)
((char*)&a)[3 - i] = buffer[i];
return a;
}
char* loadWAV(const char* fn, int& chan, int& samplerate, int& bps, int& size)
{
char buffer[4];
std::ifstream in(fn, std::ios::binary);
in.read(buffer, 4);
if (strncmp(buffer, "RIFF", 4) != 0)
{
std::cout << "this is not a valid WAVE file" << std::endl;
return NULL;
}
in.read(buffer, 4);
in.read(buffer, 4); //WAVE
in.read(buffer, 4); //fmt
in.read(buffer, 4); //16
in.read(buffer, 2); //1
in.read(buffer, 2);
chan = convertToInt(buffer, 2);
in.read(buffer, 4);
samplerate = convertToInt(buffer, 4);
in.read(buffer, 4);
in.read(buffer, 2);
in.read(buffer, 2);
bps = convertToInt(buffer, 2);
in.read(buffer, 4); //data
in.read(buffer, 4);
size = convertToInt(buffer, 4);
char* data = new char[size];
in.read(data, size);
return data;
}
int main(int argc, char** argv)
{
int channel, sampleRate, bps, size;
char* data = loadWAV("C:/Users/Gizego/Desktop/Youtube/Músicas/TheFatRat+-+Time+Lapse.wav", channel, sampleRate, bps, size);
ALCdevice* device = alcOpenDevice(NULL);
if (device == NULL)
{
std::cout << "cannot open sound card" << std::endl;
return 0;
}
ALCcontext* context = alcCreateContext(device, NULL);
if (context == NULL)
{
std::cout << "cannot open context" << std::endl;
return 0;
}
alcMakeContextCurrent(context);
unsigned int bufferid, format;
alGenBuffers(1, &bufferid);
if (channel == 1)
{
if (bps == 8)
{
format = AL_FORMAT_MONO8;
}
else {
format = AL_FORMAT_MONO16;
}
}
else {
if (bps == 8)
{
format = AL_FORMAT_STEREO8;
}
else {
format = AL_FORMAT_STEREO16;
}
}
alBufferData(bufferid, format, data, size, sampleRate);
unsigned int sourceid;
alGenSources(1, &sourceid);
alSourcei(sourceid, AL_BUFFER, bufferid);
alSourcePlay(sourceid);
while (true)
{
}
alDeleteSources(1, &sourceid);
alDeleteBuffers(1, &bufferid);
alcDestroyContext(context);
alcCloseDevice(device);
delete[] data;
return 0;
}
This question is a bit old, yet unanswered. I happen to have written a WAV file loader, and I've stumbled upon the exact same issue as you did.
As a matter of fact, the "data" part is not guaranteed to exist where you expect it to be. There are other blocks that may be specified, like in my case a "cue" one. This is kind of a really hidden information, and I've spent much time trying to find this: https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#cue
In my case, I then simply check if there is a "cue" part and simply ignore its data. I do not (yet) check for other block types, since I do not have any testing material for it.
In C++ code, this does the job:
// std::ifstream file("...", std::ios_base::in | std::ios_base::binary);
// std::array<uint8_t, 4> bytes {};
file.read(reinterpret_cast<char*>(bytes.data()), 4); // Supposed to be "data"
// A "cue " field can be specified; if so, the given amount of bytes will be ignored
if (bytes[0] == 'c' && bytes[1] == 'u' && bytes[2] == 'e' && bytes[3] == ' ') {
file.read(reinterpret_cast<char*>(bytes.data()), 4); // Cue data size
const uint32_t cueDataSize = fromLittleEndian(bytes);
file.ignore(cueDataSize);
file.read(reinterpret_cast<char*>(bytes.data()), 4); // "data"
}
// A LIST segment may be specified; see the edit below
// "data" is now properly expected
if (bytes[0] != 'd' && bytes[1] != 'a' && bytes[2] != 't' && bytes[3] != 'a')
return false;
Edit: I now have a "LIST" tag too, which is to be expected. This can be ignored the same way as the cue:
if (bytes[0] == 'L' && bytes[1] == 'I' && bytes[2] == 'S' && bytes[3] == 'T') {
file.read(reinterpret_cast<char*>(bytes.data()), 4); // List data size
const uint32_t listDataSize = fromLittleEndian(bytes);
file.ignore(listDataSize);
file.read(reinterpret_cast<char*>(bytes.data()), 4);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With