0

I am trying to read the contents of a WAV file, and to start I want to read the header to find information. According to a lot of sources, the first 44 bytes of the file contain the information.

I have inspected the file with an HEX editor and it contains the correct data, starting with "RIFF", then numbers, "WAVE" and all.

I then tried the following code to access the header values: https://gist.github.com/ranisalt/1b0dbc84ea874a04db3f

Problem is this renders completely useless information, as if I was trying to read something in the middle of the file. I get nonsense values, not consistent with those from the HEX editor:

Imgur

Please mind that even though the code is not completely correct, my problem is that the fstream read is not rendering the same bytes the file has.

I also am working on an embedded platform with very little memory, so I need code with minimal overhead, that's why I am not seeking a full fledged library.

I tried seeking to the position 0 of the file before reading, but this is even worse, reading other strange values with no meaning.

What could possibly be happening with the file reading to render such nonsense values?

ranieri
  • 2,030
  • 2
  • 21
  • 39
  • possible duplicate of [printing of a wav header in c](http://stackoverflow.com/questions/17318685/printing-of-a-wav-header-in-c) – mtrw Jun 25 '15 at 02:11

2 Answers2

1

Reading bytes from a file and using those bytes directly as bytes for numbers is implementation-defined behavior. The C++ standard makes no guarantee of the internal byte order of integers.

To start, you'll want a function that will read little endian numbers from a file and store them as native integers in memory. It might seem superfluous if you're already on a little endian machine, but there is no good reason to not read things this way, and plenty of good reasons to not blindly assume that you're running on a little endian architecture.

Next, you will want to properly follow the spec and read the sizes and offsets to parse the information, as Remy said. Blindly assuming that all wav files are laid out the same is a much worse assumption than you might think.

As far as actually troubleshooting your issue, read the file one byte at a time and print it. Make sure you're actually reading the data you're expecting. Sometimes, odd things can happen, especially on Linux.

Kaslai
  • 2,445
  • 17
  • 17
  • Good, I actually haven't read about this endianness issue anywhere. I knew the file has its own endianness but I forgot about my machine. I will try to read byte by byte and check if the values are correct. – ranieri Jun 25 '15 at 02:11
  • something I was thinking, suppose I read the first 4 bytes of the file without dealing with endianness. From what I know, the only difference would be that the value read would be backwards. Since the value is always the same (ASCII hex for "RIFF"), in the worst case it would read "FFIR". However, that is not the case. – ranieri Jun 25 '15 at 02:22
  • The first 4 bytes are not an integer to begin with, and thus they are not affected by endian, and you *should not* be reading them as an integer. But if you do, then you have to take endian into account. – Remy Lebeau Jun 25 '15 at 03:24
0

That code is NOT EVEN CLOSE to be the correct way to read a WAV file. A WAV file has structure to it, but the code is completely ignoring that structure, making inaccurate assumptions without validating them:

  • it assumes the first subchunk inside the WAVE chunk is fmt\0 - not always true!
  • it assumes the size of the fmt\0 chunk's data is exactly 16 bytes - not always true!
  • it assumes that no other chunks exist between the fmt\0 and data chunks - not always true!

You really should use a pre-existing library to read audio files, such as libav, but if you are going to do it manually, at least pay attention to what you are reading. Every chunk has a header that indicates the chunk type and data size. You MUST take those into account correctly. Read the chunks in a loop, reading each header, data payload, and optional padding as needed, checking for specific chunks that you are interested in, and ignoring other chunks that you are not interested in.

Try something more like this:

#ifndef FFT_FFT_H
#define FFT_FFT_H

#include <fstream>
#include <string>

class FFT {
public:
    FFT(const std::string& filename);

private:
    std::ifstream file;
};

#endif //FFT_FFT_H

#include "fft.h"

#include <cstdint>
#include <cstring>
#include <iostream>
#include <iomanip>
#include <vector>
#include <stdexcept> 

#pragma pack(push, 1)
struct chunkHdr
{    
    char chunkID[4];
    uint32_t dataSize;
};

struct waveFmt
{
    uint16_t wFormatTag;
    uint16_t nChannels;
    uint32_t nSamplesPerSec;
    uint32_t nAvgBytesPerSec;
    uint16_t nBlockAlign;
};

struct waveFmtEx
{
    waveFmt wf;
    uint16_t wBitsPerSample;
    uint16_t cbSize;
    // cbSize number of bytes follow this struct...
};

struct pcmWaveFmt
{
    waveFmt wf;
    uint16_t wBitsPerSample;
};

union uWaveFmt
{
    waveFmt wf;
    waveFmtEx wfx;
    pcmWaveFmt pcm;
};
#endif

void readBytes(std::ifstream &f, void *buf, std::streamsize bufsize)
{
    if (!f.read(reinterpret_cast<char*>(buf), bufsize))
        throw std::runtime_error("not enough bytes in file");
}

void skipBytes(std::ifstream &f, std::streamsize bufsize)
{
    if (!f.seekg(bufsize, std::ios_base::cur))
        throw std::runtime_error("not enough bytes in file");
}

void readChunkHeader(std::ifstream &f, chunkHdr &hdr)
{
    readBytes(f, &hdr, sizeof(hdr));
    // if you are on a big-endian system, you need to swap the bytes of hdr.dataSize here...
}

FFT::FFT(const std::string& filename)
{
    file.open(filename.c_str(), std::ifstream::binary);
    if (!file.is_open())
        throw std::runtime_error("cannot open the file");

    chunkHdr hdr;
    char riffType[4];
    std::vector<uint8_t> waveFmtBuffer;
    uWaveFmt *fmt = NULL;

    readChunkHeader(file, hdr); // should be RIFF
    if( (hdr.chunkID[0] != 'R') ||
        (hdr.chunkID[1] != 'I') ||
        (hdr.chunkID[2] != 'F') ||
        (hdr.chunkID[3] != 'F') )
        throw std::runtime_error("Expected chunk 'RIFF' not detected");

    readBytes(file, riffType, 4); // should be WAVE
    if( (riffType[0] != 'W') ||
        (riffType[1] != 'A') ||
        (riffType[2] != 'V') ||
        (riffType[3] != 'E') )
        throw std::runtime_error("Expected type 'WAVE' not detected");

    while (!file.eof())
    {
        readChunkHeader(file, hdr);

        if(
            (hdr.chunkID[0] == 'f') &&
            (hdr.chunkID[1] == 'm') &&
            (hdr.chunkID[2] == 't') &&
            (hdr.chunkID[3] == '\0') )
        {
            if (fmt)
                throw std::runtime_error("Only one 'fmt' chunk is allowed");

            if (hdr.dataSize == 0)
                throw std::runtime_error("Invalid 'fmt' data size detected");

            waveFmtBuffer.resize(hdr.dataSize);
            readBytes(file, &data[0], hdr.dataSize);
            fmt = reinterpret_cast<uWaveFmt*>(&waveFmtBuffer[0]);

            if (hdr.dataSize >= sizeof(waveFmtEx))
            {
                // if you are on a big-endian system, you need to swap the bytes of the uWaveFmt->wfx fields first...

                if (fmt->wfx.wFormatTag == 1) // PCM
                    fmt->wfx.cbSize = 0; // not used in PCM

                else if (hdr.dataSize < (sizeof(waveFmtEx) + fmt->cbSize))
                    throw std::runtime_error("Invalid 'fmt' data size detected");
            }
            else if (hdr.dataSize == sizeof(waveFmt))
            {
                // if you are on a big-endian system, you need to swap the bytes of the fmt->wf fields first...

                if (fmt->wf.wFormatTag == 1) // PCM
                    throw std::runtime_error("Invalid 'fmt' data size detected"); // needed at least sizeof(pcmWaveFmt) bytes
            }
            else
                throw std::runtime_error("Invalid 'fmt' data size detected");

            // use fmt as needed...
         }

         else if(
            (hdr.chunkID[0] == 'd') &&
            (hdr.chunkID[1] == 'a') &&
            (hdr.chunkID[2] == 't') &&
            (hdr.chunkID[3] == 'a') )
        {
            if (!fmt)
                throw std::runtime_error("'fmt' chunk not detected before 'data' chunk");

            // read exactly hdr.dataSize bytes, processing audio samples
            // as needed based on the format defined in the 'fmt' chunk...

            skipBytes(file, hdr.dataSize);
        }

        // any other chunks you are interested in...

        else
        {
            // skip everything else...
            skipBytes(file, hdr.dataSize);
        }

        // skip trailing pad byte if the data size is not even...
        if ((hdr.dataSize % 2) != 0)
            skipBytes(file, 1);
    }
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I think you completely ignored what I have written. I'm getting even the VERY FIRST BYTE incorrectly, so maybe I'm reading the middle parts incorrectly, but what about the very first byte? Why is not being read properly? – ranieri Jun 24 '15 at 23:57
  • @ranisalt: Part of that is due to you reading some of the values as integers when they are not integers to begin with. The `RIFF` and `WAVE` markers are not integers! They are actually character strings. You also have to take endian into account for the other fields that really are integers, such as the data size of each chunk, and the fields of the WAVE format header. – Remy Lebeau Jun 25 '15 at 00:40
  • I tried the code after your edit and still no luck. edit: now file.read actually fails silently, but I'm sure file size is enough. – ranieri Jun 25 '15 at 02:46
  • I discovered why the read won't work by any means: the fstream can't even open the file. I have asserted the file is there, present, in the correct directory, with proper permissions but it still can not open the file. Any clue why? – ranieri Jun 25 '15 at 03:09
  • @ranisalt: That is behind the scope of this original question. Post a new question about that specific issue. Most likely, your `filename` is not trying to open the file you are expecting. – Remy Lebeau Jun 25 '15 at 03:23