c++audiowavriff

error in reading a wav file with C++


I have a .wav file and I want to read it in C++. I have done some research on the RIFF file header and wrote a code to load it.

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

using namespace std;
#define BUFFER_LEN 4096

int main(int argc,char * argv[])
{
    // Buffers etc..
    char ChunkID[4], Format[4], Subchunk1ID[4],Subchunk2ID[4];
    int ChunkSize,Subchunk1Size, SampleRate, ByteRate,Subchunk2Size;
    short AudioFormat, NumChannels, BlockAlign, BitsPerSample;

    // Read the wave file
    FILE *fhandle=fopen(argv[1],"rb");
    fread(ChunkID,1,4,fhandle);
    fread(&ChunkSize,4,1,fhandle);
    fread(Format,1,4,fhandle);
    fread(Subchunk1ID,1,4,fhandle);
    fread(&Subchunk1Size,4,1,fhandle);
    fread(&AudioFormat,2,1,fhandle);
    fread(&NumChannels,2,1,fhandle);
    fread(&SampleRate,4,1,fhandle);
    fread(&ByteRate,4,1,fhandle);
    fread(&BlockAlign,2,1,fhandle);
    fread(&BitsPerSample,2,1,fhandle);
    fread(&Subchunk2ID,1,4,fhandle);
    fread(&Subchunk2Size,4,1,fhandle);
    fclose(fhandle);

    // print RIFF info
    printf("\%c",ChunkID[0]);
    printf("\%c",ChunkID[1]);
    printf("\%c",ChunkID[2]);
    printf("\%c",ChunkID[3]);
    cout << endl;

    // print chunk size
    printf("%d",ChunkSize);
    cout << endl;

    // print format
    printf("\%c",Format[0]);
    printf("\%c",Format[1]);
    printf("\%c",Format[2]);
    printf("\%c",Format[3]);
    cout << endl;

    // print sub chunk 1 ID
    printf("\%c",Subchunk1ID[0]);
    printf("\%c",Subchunk1ID[1]);
    printf("\%c",Subchunk1ID[2]);
    printf("\%c",Subchunk1ID[3]);
    cout << endl;

    // print sub chunk 1 size
    printf("%d",Subchunk1Size);
    cout << endl;

    // print audio format
    printf("%hd",AudioFormat);
    cout << endl;

    // print number of channels
    printf("%hd",NumChannels);
    cout << endl;    

    return 0;
}

However, it is very wired that I run this code on my input file OS.wav. It output the information:

RIFF
307201488
WAVE
Fake
2
0
28006

You can see the "fmt" is "Fake".

Then I use sox to convert this wav file by:

sox OS.wav test.wav

and run my code again. It has the information:

RIFF
307200050
WAVE
fmt 
18
3
2

I did not change any thing. But the header information is so different. Could anyone tell me why this happen, please?

Thank you.


Solution

  • You are assuming the fmt chunk is the first sub-chunk within the RIFF/WAVE chunk. That is not a requirement or a guarantee. Do some more research on the RIFF/WAV format. WAVE sub-chunks can appear in any order, the only rule being that the fmt chunk must appear before the data chunk, but other chunks can appear before, after, and in between them.

    The correct way to parse your file is to loop through the sub-chunks one at a time. Read a chunkID and chunkSize, then read the specified number of bytes (taking padding into account) and process them according to the chunkID as needed, and then repeat until EOF. This lets you process the chunks you are interested in and skip the ones you don't care about. Don't make any assumptions about their order (but do validate the one special case), and definitely don't assume the fmt chunk is the first chunk, because it might not be.

    Try something more like this:

    #include <iostream>
    #include <istream>
    #include <stdexcept>
    #include <string>
    
    using namespace std;
    
    struct chunkHdr
    {
       char id[4];
       unsigned int size;
       unsigned int pos;
    };
    
    bool isChunkID(const chunkHdr &c, char id1, char id2, char id3, char id4)
    {
        return ((c.id[0] == id1) &&
                (c.id[1] == id2) &&
                (c.id[2] == id3) &&
                (c.id[3] == id4));
    }
    
    void read(ifstream &f, void *buffer, streamsize size, chunkHdr *parent)
    {
        if (!f.read(static_cast<char*>(buffer), size))
        {
            if (f.eof())
                throw runtime_error("Unexpected EOF while reading from file");
            throw runtime_error("Unable to read from file");
        }
        if (parent) parent->pos += size;
    }
    
    void skip(ifstream &f, streamsize size, chunkHdr *parent)
    {
        if (!f.seekg(size, ios_base::cur))
            throw runtime_error("Unable to read from file");
        if (parent) parent->pos += size;
    }
    
    void read(ifstream &f, chunkHdr &c, chunkHdr *parent)
    {
        read(f, c.id, 4, parent);
        read(f, &(c.size), 4, parent);
        c.pos = 0;
    }
    
    int main(int argc, char * argv[])
    {
        // Buffers etc..
        chunk riff, wave, chk;
        bool fmtFound = false;
    
        try
        {
            // Open the wave file
            ifstream wavFile(argv[1], ios_base::binary);
            if (!wavFile)
                throw runtime_error("Unable to open file");
    
            // check the RIFF header
            read(wavFile, riff, NULL);
            if (!isChunkID(riff, 'R', 'I', 'F', 'F'))
                throw runtime_error("File is not a RIFF file");
            cout << "RIFF Size: " << riff.size << endl;
    
            // check the WAVE header
            read(wavFile, wave.id, 4, &riff);
            wave.size = riff.size - 4;
            wave.pos = 0;
    
            cout << "RIFF Type: '" << string(wave.id, 4) << "'" << endl;
            if (!isChunkID(wave, 'W', 'A', 'V', 'E'))
                throw runtime_error("File is not a WAV file");
    
            // read WAVE chunks
            while (wave.pos < wave.size)
            {
                read(wavFile, chk, &wave);
                cout << "Chunk: '" << string(chk.id, 4) << "', Size: " << chk.size << endl;
    
                if (isChunkID(chk, 'f', 'm', 't', ' '))
                {
                    if (fmtFound)
                        throw runtime_error("More than one FMT chunk encountered");
                    fmtFound = true;
    
                    unsigned int SampleRate, ByteRate;
                    unsigned short AudioFormat, NumChannels, BlockAlign, BitsPerSample, ExtraSize;
    
                    read(wavFile, &AudioFormat, 2, &chk);
                    read(wavFile, &NumChannels, 2, &chk);
                    read(wavFile, &SampleRate, 4, &chk);
                    read(wavFile, &ByteRate, 4, &chk);
                    read(wavFile, &BlockAlign, 2, &chk);
    
                    cout << "  Audio Format: " << AudioFormat << endl;
                    cout << "  Channels: " << NumChannels << endl;
                    cout << "  Sample Rate: " << SampleRate << endl;
                    cout << "  Byte Rate: " << ByteRate << endl;
                    cout << "  BlockAlign: " << BlockAlign << endl;
    
                    if (chk.size >= 16)
                    {
                        read(wavFile, &BitsPerSample, 2, &chk);
                        cout << "  Bits per Sample: " << BitsPerSample << endl;
                    }
    
                    if (chk.size >= 18)
                    {
                        read(wavFile, &ExtraSize, 2, &chk);
                        cout << "  Extra Size: " << ExtraSize << endl;
    
                        if (ExtraSize > 0)
                        {
                            // read and process ExtraSize number of bytes as needed...
                            skip(wavFile, ExtraSize, &chk);
                        }
                    }
    
                    if (chk.pos < chk.size)
                        skip(wavFile, chk.size - chk.pos, &chk);
                }
                else if (isChunkID(chk, 'd', 'a', 't', 'a'))
                {
                    if (!fmtFound)
                        throw runtime_error("No FMT chunk encountered before DATA chunk");
    
                    // read and process chk.size number of bytes as needed...
                    skip(wavFile, chk.size, &chk);
                }
    
                // read other chunks as needed...
    
                else
                {
                    // skip an unwanted chunk
                    skip(wavFile, chk.size, &chk);
                }
    
                // all done with this chunk
                wave.pos += chk.pos;
    
                // check for chunk padding
                if (chk.size % 2)
                    skip(wavFile, 1, &wave);
            }
        }
        catch (const exception &e)
        {
            cout << "Error! " << e.what() << endl;
        }
    
        return 0;
    }