简体   繁体   中英

Reading WAV header renders wrong data

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:

伊姆古尔

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?

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.

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);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM