Parse the .wav audio file

Now that we have discussed the .wav file format, the initial audio setup, and have our .wav file open in the application, we can process the .wav file based on the structure discussed earlier. First in the .wav file is the RIFF descriptor chunk.

Handle the RIFF descriptor

To handle the initial RIFF header, you can use a typedef structure that is similar to the following:

typedef struct
{
    char    Riff[4]; /*4 x 1 byte characters = 4 bytes*/
    long    Size;    /*4 byte long*/
    char    Wave[4]; /*4 x 1 byte characters = 4 bytes*/
}
riff_hdr;

To read the RIFF header, you can write a function similar to the one that appears below. The function determines if we have a .riff file and if it contains .wav data.

int check_hdr(FILE * fp)

The function accepts the parameterFILE *fp, which is an open file pointer to the .wav file. The return value is 0 on a successful run or negative on an unsuccessful run.

int
check_hdr(FILE * fp)
{
    riff_hdr riff_header = { "", 0 };

    /* Read the header and make sure that this is indeed a Wave file. */
    if (fread((unsigned char *) &riff_header, sizeof(riff_hdr), 1, fp)
            == 0)
        return 0;

    if (strncmp(riff_header.Riff, riff_id, strlen(riff_id)) ||
        strncmp(riff_header.Wave, wave_id, strlen(wave_id)))
        return -1;

    return 0;
}

In our calling function, we call check_hdr(). If our function returns a negative value, we have an error case.

if (check_hdr(file) == -1) {
    err("check_hdr failed");
    goto fail2;
}

If we successfully return from the function, we have a valid .wav file. We can begin parsing the .wav file.

Handle chunk tags

Since each chunk begins with a tag and size, you can create a corresponding structure to handle the tags.

typedef struct
{
    char    tag[4]; /**4 x 1 byte characters = 4 bytes*/
    long    length; /*4 byte long*/
}
riff_tag;

To make reading the tags easier, we can abstract the set of statements into a function. After we locate the tag structure, we must return the length of the memory block proceeding the tag. But due to the byte order the fields are stored as, we need to convert the 32-bit (4 byte) length variable to little endian format since we have read the field as an unsigned byte order. To do so, we can use the macro ENDIAN_LE32(), which is declared in gulliver.h.

int find_tag(FILE *fp, const char *tag)

The function accepts the first parameter, FILE *fp, which is an open file pointer to the .wav file. The second parameter, const char *tag, which is a character array with the tag to look for. The value returned is the little endian version of length variable.

int 
find_tag(FILE *fp, const char *tag)
{
    int ret_val = 0;
    riff_tag tag_bfr = { "", 0 };

    /* Keep reading until we find the tag or hit the end of file. */
    while (fread((unsigned char *) &tag_bfr, sizeof(tag_bfr), 1, fp)) {

        /* If this is our tag, set the length and break. */
        if (strncmp(tag, tag_bfr.tag, sizeof tag_bfr.tag) == 0) {
            ret_val = ENDIAN_LE32(tag_bfr.length);
            break;
        }
        fseek(fp, tag_bfr.length, SEEK_CUR);
    }

    return (ret_val);
}

Handle the format chunk

To handle the format chunk, we use a typedef structure that is similar to the following:

typedef struct
{
    short   format_tag;
    short   channels;
    long    samples_per_sec;
    long    avg_bytes_per_sec;
    short   block_align;
    short   bits_per_sample;
}
wave_hdr;

Now that we have defined the structure for the format chunk, we should declare an instance of the structure in the global scope.

wave_hdr wav_header;
In our calling function, first we find the format tag ("fmt ") using our function find_tag(). Due to the extra parameter space for non-PCM format, we must skip over the length of the format chunk - the size of wave_hdr from the current position of the file pointer.
samples = find_tag(file, "fmt ");
fread(&wav_header, sizeof(wav_header), 1, file);
fseek(file,(samples - sizeof(wave_hdr)), SEEK_CUR);

Now that we obtained our .wav information, let's display some information to the user. You should also note that you must convert the variables to little endian format.

sample_rate = ENDIAN_LE32(wav_header.samples_per_sec);
sample_channels = ENDIAN_LE16(wav_header.channels);
sample_bits = ENDIAN_LE16(wav_header.bits_per_sample);

snprintf(msg, MSG_SIZE, "SampleRate = %d, channels = %d,
    SampleBits = %d\n", sample_rate, sample_channels,
    sample_bits);
show_dialog_message(msg);