Tutorial: Loading Wave files in OpenAL without ALUT

http://www.dunsanyinteractive.com/blogs/oliver/?p=72

This week I have been busy reworking certain parts of the audio code in our Adventure game engine. Recently I discovered that ALUT has now become deprecated on ios, which was annoying as we were using OpenAL and ALUT for the audio on our Mac version. So this week I spent the time to replace all the ALUT functions in my code with my own functions that do the same thing. This was actually quite easy to do but I noticed that there was very little information on how to go about doing this on the Internet. So with that in mind I decided to write my own tutorial to help anyone that might be going through what I was going through when I started. The full function can be found at the bottom of the article.

The most important thing before we start is to understand the structure of a wave file. I used to think that it was just raw audio and didn’t imagine it stored much else other then a tiny bit of data to identify what it is and how to play it. Although its not far off from that, I found out that it actually held quite a bit of data. I have made diagram below to show all the data that is stored within the wave file. A more comprehensive version can be found Here. My diagram was based off of the information from that link.

Tutorial: Loading Wave files in OpenAL without ALUT_第1张图片So if you look at the diagram above you will notice that our wave files are structured in a particular way, with each piece being of a particular size. An efficient way for us to read that data is for us to make a struct replicating that data structure. For the example, I have made 3 different structs, which I think was nice for readability and to separate the different parts of data. This also allows me to check for errors more efficiently as I can see which part had the issue reading in the data. You also have to be careful as sometimes there can be an extra parameter, which I also check for in this example.

Below is the code for the first struct. The data in here can pretty much be ignored, but this data will determine if the file being read in is indeed a wave. The first block of 4 bytes will store the characters “RIFF” which stands for the Resource Interchange File Format. This is basically a structure for holding chunks of data which you can see is the case with wave files. Finally format will store the letters “WAVE” which will make sure that the file loaded is a wave file. It would be good idea to check the values of these two blocks to simply make sure that this is an audio file and not something like a video file.

1
2
3
4
5
6
7
8
9
10
/*
  * Struct that holds the RIFF data of the Wave file.
  * The RIFF data is the meta data information that holds,
  * the ID, size and format of the wave file
  */
struct RIFF_Header {
   char chunkID[4];
   long chunkSize; //size not including chunkSize or chunkID
   char format[4];
};

The next block of data is where the  structs become a little bit more relevant, as this is what we will need to know to play the wave file. The main pieces of data that we are interested in will be numChannels, sampleRate and bitsPerSample. In this example we will also be using subChunkID for some very simple error checking and subChunkSize to determine if there are any extra parameters.

1
2
3
4
5
6
7
8
9
10
11
12
13
/*
  * 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;
};

Finally we have the last struct that we will need, which stores the information relevant to the actual sound data that follows.

1
2
3
4
5
6
7
/*
* 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
};

So now we are finally ready to write our function that will take in a wave file and load it into an assigned buffer. The first thing we do is pass in a few arguments. In my example I pass in the filename, the openAL buffer to store our sound and all the other things needed by OpenAL to play our file. These files do not need to be initialised, I declare them in the function that calls loadWavFile. It is not necessary to pass in all those arguments as you can create them locally. The only thing that will be used outside the function will be our buffer.

The first thing that is done in the function is declare some local variables. I use a char* to hold my sound data while the rest is handled by our structs that we defined earlier on. We will also need a file handle so I declare a FILE*.

From here it is quite simple. We open the file and set it in read-only binary mode with the “rb” argument and then simply read in data for each struct. The first struct will tell us if the file we are reading is a Wave format. Make sure to include some code that checks that. My example uses a very simplistic error check that could be improved upon greatly. Following on we read in the next struct which has our more important data.

Here is where we need to make sure to check something. Sometimes wave files can have a small amount of extra space reserved for extra parameters. This can offset our data being read into our next struct so it is good to make sure there is no extra parameters. A quick way to do this is to see if subChunkSize is bigger then 16 bytes. If it is then we should get our file pointer to skip the extra parameters. For my example I assume that we will only ever get 2 bytes of extra data but a proper check should be done as to not run into any errors. Finally we read in the last struct; wave_data. This should store the letters “data” and the size of the data block which we will need to set the size parameter. I use the data subChunkID to make sure that it was loaded into memory correctly.

So finally we have reached the actual sound data which is what is stored from this point onward. Initialise the data variable to the size of the datachunk and then read in the data into it. Most of the work is now done for us at this point so what we do now is set the  function parameters correctly as we see below.

1
2
3
4
5
6
7
8
9
10
11
12
13
*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;
}

so as you see the code is pretty straight forward. We put in the size of the data block for the size variable and we use sampleRate for the frequency. the more complex variable to set is format which requires you to read in two pieces of data before you can set it. first do a check if numChannels is either a 1 or 2. If it is a 1, that means it’s is mono, if it is a 2 then it is stereo. After that we need to check the bitsPerSample variable for either an 8 or a 16. With that information we set format to one of the openAL constants as you can see above.

Finally we create a sound buffer using alGenBuffers and then we set our data to it using the alBufferData function. With that done we have loaded a wave file into memory without using ALUT. All we need to do to play it is attach a source to the buffer and play it as you would normally had done if you had used ALUT. Just remember to close our file and we are done. Below is the function in full which I hope will be helpful. I have commented it out to be as helpful as possible. This is the furst tutorial I have ever written so I hope that it was easy enough to understand. Please feel free to ask any questions or simply comment with a suggestion for improvements.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/*
  * Load wave file function. No need for ALUT with this
  */
bool loadWavFile( const 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;
 
   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(1, buffer);
     errorCheck();
     //now we put our data into the openAL buffer and
     //check for success
     alBufferData(*buffer, *format, ( void *)data,
                  *size, *frequency);
     errorCheck();
     //clean up and return true if successful
     fclose (soundFile);
     return true ;
   } catch (std::string 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
     return false ;
   }
}

你可能感兴趣的:(OpenAL,Wave)