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.

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

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

wave.c

/**
 * Read and parse a wave file
 *
 **/
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "wave.h"
#define TRUE 1 
#define FALSE 0

// WAVE header structure

unsigned char buffer4[4];
unsigned char buffer2[2];

char* seconds_to_time(float seconds);


 FILE *ptr;
 char *filename;
 struct HEADER header;

int main(int argc, char **argv) {

 filename = (char*) malloc(sizeof(char) * 1024);
 if (filename == NULL) {
   printf("Error in malloc\n");
   exit(1);
 }

 // get file path
 char cwd[1024];
 if (getcwd(cwd, sizeof(cwd)) != NULL) {
   
	strcpy(filename, cwd);

	// get filename from command line
	if (argc < 2) {
	  printf("No wave file specified\n");
	  return;
	}
	
	strcat(filename, "/");
	strcat(filename, argv[1]);
	printf("%s\n", filename);
 }

 // open file
 printf("Opening  file..\n");
 ptr = fopen(filename, "rb");
 if (ptr == NULL) {
	printf("Error opening file\n");
	exit(1);
 }
 
 int read = 0;
 
 // read header parts

 read = fread(header.riff, sizeof(header.riff), 1, ptr);
 printf("(1-4): %s \n", header.riff); 

 read = fread(buffer4, sizeof(buffer4), 1, ptr);
 printf("%u %u %u %u\n", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);
 
 // convert little endian to big endian 4 byte int
 header.overall_size  = buffer4[0] | 
						(buffer4[1]<<8) | 
						(buffer4[2]<<16) | 
						(buffer4[3]<<24);

 printf("(5-8) Overall size: bytes:%u, Kb:%u \n", header.overall_size, header.overall_size/1024);

 read = fread(header.wave, sizeof(header.wave), 1, ptr);
 printf("(9-12) Wave marker: %s\n", header.wave);

 read = fread(header.fmt_chunk_marker, sizeof(header.fmt_chunk_marker), 1, ptr);
 printf("(13-16) Fmt marker: %s\n", header.fmt_chunk_marker);

 read = fread(buffer4, sizeof(buffer4), 1, ptr);
 printf("%u %u %u %u\n", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);

 // convert little endian to big endian 4 byte integer
 header.length_of_fmt = buffer4[0] |
							(buffer4[1] << 8) |
							(buffer4[2] << 16) |
							(buffer4[3] << 24);
 printf("(17-20) Length of Fmt header: %u \n", header.length_of_fmt);

 read = fread(buffer2, sizeof(buffer2), 1, ptr); printf("%u %u \n", buffer2[0], buffer2[1]);
 
 header.format_type = buffer2[0] | (buffer2[1] << 8);
 char format_name[10] = "";
 if (header.format_type == 1)
   strcpy(format_name,"PCM"); 
 else if (header.format_type == 6)
  strcpy(format_name, "A-law");
 else if (header.format_type == 7)
  strcpy(format_name, "Mu-law");

 printf("(21-22) Format type: %u %s \n", header.format_type, format_name);

 read = fread(buffer2, sizeof(buffer2), 1, ptr);
 printf("%u %u \n", buffer2[0], buffer2[1]);

 header.channels = buffer2[0] | (buffer2[1] << 8);
 printf("(23-24) Channels: %u \n", header.channels);

 read = fread(buffer4, sizeof(buffer4), 1, ptr);
 printf("%u %u %u %u\n", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);

 header.sample_rate = buffer4[0] |
						(buffer4[1] << 8) |
						(buffer4[2] << 16) |
						(buffer4[3] << 24);

 printf("(25-28) Sample rate: %u\n", header.sample_rate);

 read = fread(buffer4, sizeof(buffer4), 1, ptr);
 printf("%u %u %u %u\n", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);

 header.byterate  = buffer4[0] |
						(buffer4[1] << 8) |
						(buffer4[2] << 16) |
						(buffer4[3] << 24);
 printf("(29-32) Byte Rate: %u , Bit Rate:%u\n", header.byterate, header.byterate*8);

 read = fread(buffer2, sizeof(buffer2), 1, ptr);
 printf("%u %u \n", buffer2[0], buffer2[1]);

 header.block_align = buffer2[0] |
					(buffer2[1] << 8);
 printf("(33-34) Block Alignment: %u \n", header.block_align);

 read = fread(buffer2, sizeof(buffer2), 1, ptr);
 printf("%u %u \n", buffer2[0], buffer2[1]);

 header.bits_per_sample = buffer2[0] |
					(buffer2[1] << 8);
 printf("(35-36) Bits per sample: %u \n", header.bits_per_sample);

 read = fread(header.data_chunk_header, sizeof(header.data_chunk_header), 1, ptr);
 printf("(37-40) Data Marker: %s \n", header.data_chunk_header);

 read = fread(buffer4, sizeof(buffer4), 1, ptr);
 printf("%u %u %u %u\n", buffer4[0], buffer4[1], buffer4[2], buffer4[3]);

 header.data_size = buffer4[0] |
				(buffer4[1] << 8) |
				(buffer4[2] << 16) | 
				(buffer4[3] << 24 );
 printf("(41-44) Size of data chunk: %u \n", header.data_size);


 // calculate no.of samples
 long num_samples = (8 * header.data_size) / (header.channels * header.bits_per_sample);
 printf("Number of samples:%lu \n", num_samples);

 long size_of_each_sample = (header.channels * header.bits_per_sample) / 8;
 printf("Size of each sample:%ld bytes\n", size_of_each_sample);

 // calculate duration of file
 float duration_in_seconds = (float) header.overall_size / header.byterate;
 printf("Approx.Duration in seconds=%f\n", duration_in_seconds);
 printf("Approx.Duration in h:m:s=%s\n", seconds_to_time(duration_in_seconds));



 // read each sample from data chunk if PCM
 if (header.format_type == 1) { // PCM
    printf("Dump sample data? Y/N?");
	char c = 'n';
	scanf("%c", &c);
	if (c == 'Y' || c == 'y') { 
		long i =0;
		char data_buffer[size_of_each_sample];
		int  size_is_correct = TRUE;

		// make sure that the bytes-per-sample is completely divisible by num.of channels
		long bytes_in_each_channel = (size_of_each_sample / header.channels);
		if ((bytes_in_each_channel  * header.channels) != size_of_each_sample) {
			printf("Error: %ld x %ud <> %ld\n", bytes_in_each_channel, header.channels, size_of_each_sample);
			size_is_correct = FALSE;
		}
 
		if (size_is_correct) { 
					// the valid amplitude range for values based on the bits per sample
			long low_limit = 0l;
			long high_limit = 0l;

			switch (header.bits_per_sample) {
				case 8:
					low_limit = -128;
					high_limit = 127;
					break;
				case 16:
					low_limit = -32768;
					high_limit = 32767;
					break;
				case 32:
					low_limit = -2147483648;
					high_limit = 2147483647;
					break;
			}					

			printf("\n\n.Valid range for data values : %ld to %ld \n", low_limit, high_limit);
			for (i =1; i <= num_samples; i++) {
				printf("==========Sample %ld / %ld=============\n", i, num_samples);
				read = fread(data_buffer, sizeof(data_buffer), 1, ptr);
				if (read == 1) {
				
					// dump the data read
					unsigned int  xchannels = 0;
					int data_in_channel = 0;

					for (xchannels = 0; xchannels < header.channels; xchannels ++ ) {
						printf("Channel#%d : ", (xchannels+1));
						// convert data from little endian to big endian based on bytes in each channel sample
						if (bytes_in_each_channel == 4) {
							data_in_channel =	data_buffer[0] | 
												(data_buffer[1]<<8) | 
												(data_buffer[2]<<16) | 
												(data_buffer[3]<<24);
						}
						else if (bytes_in_each_channel == 2) {
							data_in_channel = data_buffer[0] |
												(data_buffer[1] << 8);
						}
						else if (bytes_in_each_channel == 1) {
							data_in_channel = data_buffer[0];
						}

						printf("%d ", data_in_channel);

						// check if value was in range
						if (data_in_channel < low_limit || data_in_channel > high_limit)
							printf("**value out of range\n");

						printf(" | ");
					}

					printf("\n");
				}
				else {
					printf("Error reading file. %d bytes\n", read);
					break;
				}

			} // 	for (i =1; i <= num_samples; i++) {

		} // 	if (size_is_correct) { 

	 } // if (c == 'Y' || c == 'y') { 
 } //  if (header.format_type == 1) { 

 printf("Closing file..\n");
 fclose(ptr);

  // cleanup before quitting
 free(filename);
 return 0;

}

/**
 * Convert seconds into hh:mm:ss format
 * Params:
 *	seconds - seconds value
 * Returns: hms - formatted string
 **/
 char* seconds_to_time(float raw_seconds) {
  char *hms;
  int hours, hours_residue, minutes, seconds, milliseconds;
  hms = (char*) malloc(100);

  sprintf(hms, "%f", raw_seconds);

  hours = (int) raw_seconds/3600;
  hours_residue = (int) raw_seconds % 3600;
  minutes = hours_residue/60;
  seconds = hours_residue % 60;
  milliseconds = 0;

  // get the decimal part of raw_seconds to get milliseconds
  char *pos;
  pos = strchr(hms, '.');
  int ipos = (int) (pos - hms);
  char decimalpart[15];
  memset(decimalpart, ' ', sizeof(decimalpart));
  strncpy(decimalpart, &hms[ipos+1], 3);
  milliseconds = atoi(decimalpart);	

  
  sprintf(hms, "%d:%d:%d.%d", hours, minutes, seconds, milliseconds);
  return hms;
}

A sample run is given below:

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

34 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.

Leave a Reply

Your email address will not be published.


*


This site uses Akismet to reduce spam. Learn how your comment data is processed.