Parse the .wav audio file

Now that we have discussed the .wav file format, set up the initial audio components, and opened our .wav file in the application, we can process the .wav file based on the structure we discussed earlier in Structure of a .wav file. First in the .wav file is the .riff chunk descriptor.

Handle the .riff chunk descriptor

To handle the .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 check_hdr() function that appears below. This function determines if we have a .riff file and whether it contains .wav data. The function accepts the parameter FILE * fp, which is a pointer to the .wav file. The return value is 0 on a successful run and a negative value 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 our function doesn't return a negative value, we have a valid .wav file and we can begin to parse it.

Handle chunk tags

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

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

To make it easier to read the tags, 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 that appears before the tag. Because of the byte order the fields are stored as, we need to convert the 32-bit (4-byte) length variable to little-endian format to read the field as an unsigned byte order. To perform this conversion, we can use the macro ENDIAN_LE32(), which is declared in gulliver.h.

The find_tag() function below accepts the first parameter, FILE *fp, which is an open file pointer to the .wav file. The second parameter, const char *tag, is a character array that represents the tag that we want to search for. The value returned is the little-endian version of the 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 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 find_tag(). Because non-PCM format uses extra parameter space, we must skip over the length of the format chunk, which is 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 have our .wav information, let's display some information to the user. Remember 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);

Last modified: 2013-12-21

comments powered by Disqus