Parsing a WAV file in C

The WAV (or PCM) audio format is the most basic format for storing audio. WAV files can be of different extended formats , but PCM is the most popular and common. The other formats are A-law and Mu-law. The PCM format stores raw audio data without any compression or conversion, thus leading to the largest file sizes, as compared to other formats like AIFF or MP3 or OGG.

While there are existing libraries in several languages which allow you to work with WAV files, this post is an attempt to understand how to read the WAV file format without any external library. The language used here is C, and has been compiled using GCC under Linux, but it can be easily run under Windows also with minimal modifications. Most likely for VC++ you will have to replace #include <unistd.h> with #include <io.h>

WAV HEADER STRUCTURE

The header structure is 44 bytes long and has the following structure:

 

Positions Sample Value Description
1 – 4 “RIFF” Marks the file as a riff file. Characters are each 1 byte long.
5 – 8 File size (integer) Size of the overall file – 8 bytes, in bytes (32-bit integer). Typically, you’d fill this in after creation.
9 -12 “WAVE” File Type Header. For our purposes, it always equals “WAVE”.
13-16 “fmt “ Format chunk marker. Includes trailing null
17-20 16 Length of format data as listed above
21-22 1 Type of format (1 is PCM) – 2 byte integer
23-24 2 Number of Channels – 2 byte integer
25-28 44100 Sample Rate – 32 byte integer. Common values are 44100 (CD), 48000 (DAT). Sample Rate = Number of Samples per second, or Hertz.
29-32 176400 (Sample Rate * BitsPerSample * Channels) / 8.
33-34 4 (BitsPerSample * Channels) / 8.1 – 8 bit mono2 – 8 bit stereo/16 bit mono4 – 16 bit stereo
35-36 16 Bits per sample
37-40 “data” “data” chunk header. Marks the beginning of the data section.
41-44 File size (data) Size of the data section.
Sample values are given above for a 16-bit stereo source.

It is important to note that the WAV  format uses little-endian format to store bytes, so you need to convert the bytes to big-endian in code for the values to make sense.

EDIT: AUG 2020

Thanks to a bug pointed out by Kalpathi Subramanian, the code has been updated to rectify the bug. He has adapted this code into a C++ implementation which is available on https://github.com/BridgesUNCC/bridges-cxx/blob/master/src/AudioClip.h

CODE

The code consists of a header file wave.h which is included in wave.c . Once you compile it and run it, it accepts the path of a wav file from the command line and dumps the structure information including the size of each sample and the total duration of the wav audio.

wave.h

01// WAVE file header format
02struct HEADER {
03    unsigned char riff[4];                      // RIFF string
04    unsigned int overall_size   ;               // overall size of file in bytes
05    unsigned char wave[4];                      // WAVE string
06    unsigned char fmt_chunk_marker[4];          // fmt string with trailing null char
07    unsigned int length_of_fmt;                 // length of the format data
08    unsigned int format_type;                   // format type. 1-PCM, 3- IEEE float, 6 - 8bit A law, 7 - 8bit mu law
09    unsigned int channels;                      // no.of channels
10    unsigned int sample_rate;                   // sampling rate (blocks per second)
11    unsigned int byterate;                      // SampleRate * NumChannels * BitsPerSample/8
12    unsigned int block_align;                   // NumChannels * BitsPerSample/8
13    unsigned int bits_per_sample;               // bits per sample, 8- 8bits, 16- 16 bits etc
14    unsigned char data_chunk_header [4];        // DATA string or FLLR string
15    unsigned int data_size;                     // NumSamples * NumChannels * BitsPerSample/8 - size of the next chunk that will be read
16};

wave.c

001/**
002 * Read and parse a wave file
003 *
004 **/
005#include <unistd.h>
006#include <stdio.h>
007#include <string.h>
008#include <stdlib.h>
009#include "wave.h"
010#define TRUE 1
011#define FALSE 0
012 
013// WAVE header structure
014 
015unsigned char buffer4[4];
016unsigned char buffer2[2];
017 
018char* seconds_to_time(float seconds);
019 
020 
021 FILE *ptr;
022 char *filename;
023 struct HEADER header;
024 
025int main(int argc, char **argv) {
026 
027 filename = (char*) malloc(sizeof(char) * 1024);
028 if (filename == NULL) {
029   printf("Error in mallocn");
030   exit(1);
031 }
032 
033 // get file path
034 char cwd[1024];
035 if (getcwd(cwd, sizeof(cwd)) != NULL) {
036    
037    strcpy(filename, cwd);
038 
039    // get filename from command line
040    if (argc < 2) {
041      printf("No wave file specifiedn");
042      return;
043    }
044     
045    strcat(filename, "/");
046    strcat(filename, argv[1]);
047    printf("%sn", filename);
048 }
049 
050 // open file
051 printf("Opening  file..n");
052 ptr = fopen(filename, "rb");
053 if (ptr == NULL) {
054    printf("Error opening filen");
055    exit(1);
056 }
057  
058 int read = 0;
059  
060 // read header parts
061 
062 read = fread(header.riff, sizeof(header.riff), 1, ptr);
063 printf("(1-4): %s n", header.riff);
064 
065 read = fread(buffer4, sizeof(buffer4), 1, ptr);
066 printf("%u %u %u %un", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);
067  
068 // convert little endian to big endian 4 byte int
069 header.overall_size  = buffer4[0] |
070                        (buffer4[1]<<8) |
071                        (buffer4[2]<<16) |
072                        (buffer4[3]<<24);
073 
074 printf("(5-8) Overall size: bytes:%u, Kb:%u n", header.overall_size, header.overall_size/1024);
075 
076 read = fread(header.wave, sizeof(header.wave), 1, ptr);
077 printf("(9-12) Wave marker: %sn", header.wave);
078 
079 read = fread(header.fmt_chunk_marker, sizeof(header.fmt_chunk_marker), 1, ptr);
080 printf("(13-16) Fmt marker: %sn", header.fmt_chunk_marker);
081 
082 read = fread(buffer4, sizeof(buffer4), 1, ptr);
083 printf("%u %u %u %un", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);
084 
085 // convert little endian to big endian 4 byte integer
086 header.length_of_fmt = buffer4[0] |
087                            (buffer4[1] << 8) |
088                            (buffer4[2] << 16) |
089                            (buffer4[3] << 24);
090 printf("(17-20) Length of Fmt header: %u n", header.length_of_fmt);
091 
092 read = fread(buffer2, sizeof(buffer2), 1, ptr); printf("%u %u n", buffer2[0], buffer2[1]);
093  
094 header.format_type = buffer2[0] | (buffer2[1] << 8);
095 char format_name[10] = "";
096 if (header.format_type == 1)
097   strcpy(format_name,"PCM");
098 else if (header.format_type == 6)
099  strcpy(format_name, "A-law");
100 else if (header.format_type == 7)
101  strcpy(format_name, "Mu-law");
102 
103 printf("(21-22) Format type: %u %s n", header.format_type, format_name);
104 
105 read = fread(buffer2, sizeof(buffer2), 1, ptr);
106 printf("%u %u n", buffer2[0], buffer2[1]);
107 
108 header.channels = buffer2[0] | (buffer2[1] << 8);
109 printf("(23-24) Channels: %u n", header.channels);
110 
111 read = fread(buffer4, sizeof(buffer4), 1, ptr);
112 printf("%u %u %u %un", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);
113 
114 header.sample_rate = buffer4[0] |
115                        (buffer4[1] << 8) |
116                        (buffer4[2] << 16) |
117                        (buffer4[3] << 24);
118 
119 printf("(25-28) Sample rate: %un", header.sample_rate);
120 
121 read = fread(buffer4, sizeof(buffer4), 1, ptr);
122 printf("%u %u %u %un", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);
123 
124 header.byterate  = buffer4[0] |
125                        (buffer4[1] << 8) |
126                        (buffer4[2] << 16) |
127                        (buffer4[3] << 24);
128 printf("(29-32) Byte Rate: %u , Bit Rate:%un", header.byterate, header.byterate*8);
129 
130 read = fread(buffer2, sizeof(buffer2), 1, ptr);
131 printf("%u %u n", buffer2[0], buffer2[1]);
132 
133 header.block_align = buffer2[0] |
134                    (buffer2[1] << 8);
135 printf("(33-34) Block Alignment: %u n", header.block_align);
136 
137 read = fread(buffer2, sizeof(buffer2), 1, ptr);
138 printf("%u %u n", buffer2[0], buffer2[1]);
139 
140 header.bits_per_sample = buffer2[0] |
141                    (buffer2[1] << 8);
142 printf("(35-36) Bits per sample: %u n", header.bits_per_sample);
143 
144 read = fread(header.data_chunk_header, sizeof(header.data_chunk_header), 1, ptr);
145 printf("(37-40) Data Marker: %s n", header.data_chunk_header);
146 
147 read = fread(buffer4, sizeof(buffer4), 1, ptr);
148 printf("%u %u %u %un", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);
149 
150 header.data_size = buffer4[0] |
151                (buffer4[1] << 8) |
152                (buffer4[2] << 16) |
153                (buffer4[3] << 24 );
154 printf("(41-44) Size of data chunk: %u n", header.data_size);
155 
156 
157 // calculate no.of samples
158 long num_samples = (8 * header.data_size) / (header.channels * header.bits_per_sample);
159 printf("Number of samples:%lu n", num_samples);
160 
161 long size_of_each_sample = (header.channels * header.bits_per_sample) / 8;
162 printf("Size of each sample:%ld bytesn", size_of_each_sample);
163 
164 // calculate duration of file
165 float duration_in_seconds = (float) header.overall_size / header.byterate;
166 printf("Approx.Duration in seconds=%fn", duration_in_seconds);
167 printf("Approx.Duration in h:m:s=%sn", seconds_to_time(duration_in_seconds));
168 
169 
170 
171 // read each sample from data chunk if PCM
172 if (header.format_type == 1) { // PCM
173    printf("Dump sample data? Y/N?");
174    char c = 'n';
175    scanf("%c", &c);
176    if (c == 'Y' || c == 'y') {
177        long i =0;
178        char data_buffer[size_of_each_sample];
179        int  size_is_correct = TRUE;
180 
181        // make sure that the bytes-per-sample is completely divisible by num.of channels
182        long bytes_in_each_channel = (size_of_each_sample / header.channels);
183        if ((bytes_in_each_channel  * header.channels) != size_of_each_sample) {
184            printf("Error: %ld x %ud <> %ldn", bytes_in_each_channel, header.channels, size_of_each_sample);
185            size_is_correct = FALSE;
186        }
187  
188        if (size_is_correct) {
189                    // the valid amplitude range for values based on the bits per sample
190            long low_limit = 0l;
191            long high_limit = 0l;
192 
193            switch (header.bits_per_sample) {
194                case 8:
195                    low_limit = -128;
196                    high_limit = 127;
197                    break;
198                case 16:
199                    low_limit = -32768;
200                    high_limit = 32767;
201                    break;
202                case 32:
203                    low_limit = -2147483648;
204                    high_limit = 2147483647;
205                    break;
206            }                  
207 
208            printf("nn.Valid range for data values : %ld to %ld n", low_limit, high_limit);
209            for (i =1; i <= num_samples; i++) {
210                printf("==========Sample %ld / %ld=============n", i, num_samples);
211                read = fread(data_buffer, sizeof(data_buffer), 1, ptr);
212                if (read == 1) {
213                 
214                    // dump the data read
215                    unsigned int  xchannels = 0;
216                    int data_in_channel = 0;
217                    int offset = 0; // move the offset for every iteration in the loop below
218                    for (xchannels = 0; xchannels < header.channels; xchannels ++ ) {
219                        printf("Channel#%d : ", (xchannels+1));
220                        // convert data from little endian to big endian based on bytes in each channel sample
221                        if (bytes_in_each_channel == 4) {
222                            data_in_channel = (data_buffer[offset] & 0x00ff) |
223                                                ((data_buffer[offset + 1] & 0x00ff) <<8) |
224                                                ((data_buffer[offset + 2] & 0x00ff) <<16) |
225                                                (data_buffer[offset + 3]<<24);
226                        }
227                        else if (bytes_in_each_channel == 2) {
228                            data_in_channel = (data_buffer[offset] & 0x00ff) |
229                                                (data_buffer[offset + 1] << 8);
230                        }
231                        else if (bytes_in_each_channel == 1) {
232                            data_in_channel = data_buffer[offset] & 0x00ff;
233                            data_in_channel -= 128; //in wave, 8-bit are unsigned, so shifting to signed
234                        }
235 
236                        offset += bytes_in_each_channel;       
237                        printf("%d ", data_in_channel);
238 
239                        // check if value was in range
240                        if (data_in_channel < low_limit || data_in_channel > high_limit)
241                            printf("**value out of rangen");
242 
243                        printf(" | ");
244                    }
245 
246                    printf("n");
247                }
248                else {
249                    printf("Error reading file. %d bytesn", read);
250                    break;
251                }
252 
253            } //    for (i =1; i <= num_samples; i++) {
254 
255        } //    if (size_is_correct) {
256 
257     } // if (c == 'Y' || c == 'y') {
258 } //  if (header.format_type == 1) {
259 
260 printf("Closing file..n");
261 fclose(ptr);
262 
263  // cleanup before quitting
264 free(filename);
265 return 0;
266 
267}
268 
269/**
270 * Convert seconds into hh:mm:ss format
271 * Params:
272 *  seconds - seconds value
273 * Returns: hms - formatted string
274 **/
275 char* seconds_to_time(float raw_seconds) {
276  char *hms;
277  int hours, hours_residue, minutes, seconds, milliseconds;
278  hms = (char*) malloc(100);
279 
280  sprintf(hms, "%f", raw_seconds);
281 
282  hours = (int) raw_seconds/3600;
283  hours_residue = (int) raw_seconds % 3600;
284  minutes = hours_residue/60;
285  seconds = hours_residue % 60;
286  milliseconds = 0;
287 
288  // get the decimal part of raw_seconds to get milliseconds
289  char *pos;
290  pos = strchr(hms, '.');
291  int ipos = (int) (pos - hms);
292  char decimalpart[15];
293  memset(decimalpart, ' ', sizeof(decimalpart));
294  strncpy(decimalpart, &hms[ipos+1], 3);
295  milliseconds = atoi(decimalpart);
296 
297   
298  sprintf(hms, "%d:%d:%d.%d", hours, minutes, seconds, milliseconds);
299  return hms;
300}

A sample run is given below:

Screenshot from 2015-09-05 19:29:20

53 Comments

  1. Hii Amit,

    Thanks a lot for such a nice tutorial. In fact I used your code it was working properly. I have a question if suppose the wave header is of not 44 bytes may be 48 bytes or any other length how the code structure would be?

  2. Hi Suraj,

    There are non-standard wav file formats which are used, where the header size will be more than 44 bytes. Some of the commercial audio programs and music softwares add their own undocumented data into the header. The only way to know if this is a not a 44 byte header is to check the location of the “data” chunk marker. It means that a really robust program would go on parsing the file till it finds a valid header instead of assuming that it will always be at the beginning. This is much like parsing an MP3 file where the header can be anywhere in the file.

  3. header.block_align and size_of_each_sample seem to be equivalent.
    also using header.block_align for calculating num_samples also makes for easier to understand math i.e. num_samples = header.data_size / header.block_align

  4. @mel Your statement is correct. The definition of “block align” is “The number of bytes for one sample including all channels” which makes you wonder why cant it be called something more relevant.

  5. @Jay The code uses byte shifting to convert from little endian to big endian. I am not sure if two’s complement is required here because there are no negative values involved so we dont need to worry about the sign bit. Correct me if I am wrong.

  6. can u please help me how to run this code on windows through code:block IDE???
    please reply me it’s very urgent.

  7. @sudha I have never used Code Block IDE so I cant help you in that. But this is a very simple file – one c file and which includes a header file. It should run in any IDE .

    • Hi Amit, actually this is solved but the program is giving ‘No wave file specified’ without giving me the chance to input it at the command line.

      • Also in wave.c, in line no. 42, it (eclipse IDE) is giving me an error :

        “Return without value, in function returning non-void”

  8. @Anamay , there are no built in dynamic array primitives in pure C++ . Perhaps you want to use an external library or create a class for array handling. One alternative is to use the vector class from STL.

  9. Actually I mean that which part of the program are we dealing with the actual samples of the audio file? Which variable are they stored, if so?

  10. @Amit, The actual samples I obtained from matlab are all in the range of -1 to 1 whereas the values obtained from this program are in the ranges of 10000 also. What could be the reason for that?

        • Okay, no worries.. Thanks for your help.
          Some feedback:
          In the ‘get filename from command line’ section, for my program, the variable ‘argc’ was always 1. So I manually set it to 2 to avoid getting ‘No wave file specified’. Though my wave file was in the same working directory. Also argv[1] was null. Also, I added a ‘scanf’ function there to input the file name from the keyboard.

          • Hello Amit, finally I got my output as expected. Thanks so much for this code. What solved the problem was in the part where conversion from little endian to big endian occurs, i.e. lines 220 – 232, my case being bytes_in_each_channel == 2, lines 227 to 230,

            else if (bytes_in_each_channel == 2)
            {
            data_in_channel = data_buffer[0] & 255 | (data_buffer[1] << 8);
            }

            Just added '& 255' to it. i.e. ANDing data_buffer[0] with 255 (all ones). The value of data_buffer[0], remains the same but the conversion of little endian to big endian occurs correctly even when data_buffer[0] is negative,
            something which was not happening before.

  11. Hai…
    In the command line I am giving “./a.out “, in the terminal I have seen the sample number and channel number but unable to listen the sound. Could you please suggest me what’s wrong?

  12. hello! I want help for a function that must chop a track .
    The -chop argument clears
    one audio file from one time to another.
    The result is saved in a new file named chopped-sound1.wav, where sound1.wav is the original audio file name. Below is an example of splitting a sound file from 2nd to 4th:
    $ ./wavengine-chopsound1.wav2 4

  13. Hello and thank you for the code!

    i try to run your code but
    1) I get an error form line 42 (return;)
    2) if i comment line 42 or change in to return 0; it compiles but i get
    “Opening file..
    Error opening file”

    Any ideas why is that happening;

    I run:
    gcc wave.c
    ./a.out /home/path/Test1.wav

    Thanks in andvance

  14. @outatime It looks like a standard file access error. Either the file is not present in the path provided or perhaps the folder does not have permissions to allow the file to be read. A good idea would be to use ferror() to print out the error message.

  15. Hello, thanks a lot for the code.
    I’m really starting out at sound programming and I’ve got the struct (mostly) correctly filled. But you left me wondering how i would actually play this file. Any tips?
    I’m using windows.

  16. @Marcio You are welcome. Playing a wav file is a completely different thing than parsing it. You can always use any existing player app for playing a wav file. Trying to make a player of your own can be more challenging, especially you want to do everything from scratch.

  17. Hey there,
    I have a simple but important question:
    Where is the audio data stored (in which array)? I am hesitating between data_buffer and data_in_channel.
    Could someone tell me which one it is?
    Thanks in advance!

    • Although I know that this program is not designed to actually take the audio data and play it, I would like to do just that. How would I use data_in_channel to actually play the audio? Let’s suppose that I have a function to store the audio data and play it that takes these parameters:
      (buffer to store data in, format of audio data, actual audio data, size of the audio data, frequency of the audio data)
      Could I simply take all of the information from the WAV header and input it in the function? Can I actually use the buffer data_in_channel to play the file?
      Thanks!
      The actual documentation for the function is here: https://www.openal.org/documentation/OpenAL_Programmers_Guide.pdf
      go to function alBufferData!

  18. @rpy Circuits, I had the same purpose when I wrote this code. But I got busy with other projects so never got around to actually being able play the wav data. It is definitely non-trivial and requires working closely with the soundcard. From what I can make out, playing an audio sound is a function of playing a certain number of samples for a certain period of time on a given frequency.

    There is a great article here: http://digitalsoundandmusic.com/2-3-12-modeling-sound-in-c-under-linux/ which explains how sound is modeled. Of particular interest is the paragraph:
    ******
    The sound wave is created by taking the sine of the appropriate frequency (262 Hz, for example) at 44,100 evenly-spaced intervals for one second of audio data. The value returned from the sine function is between -1 and 1. However, the sound card expects a value that is stored in one byte (i.e., 8 bits), ranging from -128 to 127. To put the value into this range, we multiply by 127 and, with the floor function, round down.
    ******

    Only sound card manufacturers deal with the actual process of creating sound, so the info about the software logic is a little hard to get and a lot of device manufacturers keep their code a proprietary secret. But for people like us who want to learn the actual process of making sound, this is the holy grail.

  19. So negative values in the signal can mess this up? I was able to run the code, and tried on a piano sequence, but it comes out noisy.. My data range is from
    -14064, 12903

    • Not really. 8-bit samples are stored as unsigned bytes, ranging from 0 to 255. 16-bit samples are stored as 2’s-complement signed integers, ranging from -32768 to 32767

  20. In line 211, you are reading into the data buffer the audio data for all channels (I was using a 2 channel example and it was 4 bytes); however inside the for loop [ line 228 ] (looping over each channel) you are always using the data for the first channel. I had to modify this so that each channel data was recorded correctly. Can you clarify? I still get a noisy data from my player, but thats a different issue.

    • Its been a few years since I wrote the code so I will have to go over it once to see if there is any issue. At first glance, what you say could be correct, because the channel loop is using the same data extract in every iteration. I believe the pointer within the data array should be incremented within the channel loop. If you have already made the change and its working fine , you could probably post the fix. I will go over the code in the next couple of days and apply your fix

  21. No, I dont have it working satisfactorily. I have a Java version that reads the wave file correctly (uses the Jave Wav related classes) (and plays fine), but the C++ version (I converted the above from C to C++) is not reading the data correctly so far (at least it doesnt match the Java version and hence the sound comes out noisy). Once I have it working, I will be happy to share – in fact we are hoping to use this this for an open source educational toolkit.

    So in short, still fighting with it!

  22. Amit:

    I got the parser working yesterday. There are two issues with your original code. One I mentioned above, where for multiple channels, you need to update the pointer. Secondly, this code wont work correctly for negative signal values. For this you need to do the following where you do the endian conversion:

    data_in_channel = data_buffer[0] & 0x00ff | (data_buffer[1] << 8)

    and similarly for the 24 and 32 bit cases. Otherwise the values are changed to incorrect values.

    I have a version for C I can send you with these changes. I also did a C++ version to work with our system. I will send a link to that with acknowledgements for your approval. . We would like to use that in an open source educational toolkit we are building.

  23. Hello, firstly excellent code and thanks for sharing but I’m having a problem. To check the correct results from matlab, each sample in matlab is stored as a double instead of an int in this example. I tried to store it as int by chaning data_in_channel to double but it just adds some zeros. Also, the values still differ from the ones parsed by matlab. Thanks for your time!

    • Hello @Dimitris, I am not sure how the results will work if the channel data is converted into double because then the bit shift operations will not work correctly. The wav data is actually supposed to be only integers, so converting them into floating point will have side-effects.

  24. When compiling the code, I received the following message:

    [Warning] this decimal constant is unsigned only in ISO C90

    So, i fix it this way:

    203 low_limit = -2147483648u;

    So, this compile fine in Windows 10 with Dev-Cpp Portable compiler.

    Thank you.

  25. Hi, I am a student studying in Korea. I wonder about the license of this code.
    Is it GNU GPL license or MIT license? Thank you for reading my comment.

1 Trackback / Pingback

  1. Code Bug Fix: Reading binary file and storing it to struct in c - TECHPRPR

Leave a Reply to amit Cancel reply

Your email address will not be published.


*