读取WAV标头会导致错误的数据

时间:2015-06-24 23:16:42

标签: c++ audio wav

我正在尝试读取WAV文件的内容,并开始我想读取标题以查找信息。根据很多消息来源,文件的前44个字节包含信息。

我用HEX编辑器检查了文件,它包含正确的数据,从" RIFF"开始,然后是数字," WAVE"和所有。

然后我尝试使用以下代码来访问标头值: https://gist.github.com/ranisalt/1b0dbc84ea874a04db3f

问题是这会呈现完全无用的信息,就像我试图读取文件中间的内容一样。我得到了无意义的值,与HEX编辑器中的值不一致:

Imgur

请注意即使代码不完全正确,我的问题是fstream读取不会呈现文件所具有的相同字节。

我也正在开发一个内存非常少的嵌入式平台,所以我需要的代码只需要很少的开销,这就是为什么我不寻求一个完整的库。

我在阅读之前尝试寻找文件的位置0,但更糟糕的是,阅读其他没有意义的奇怪值。

文件读取可能会发生什么以呈现这样的无意义值?

2 个答案:

答案 0 :(得分:1)

从文件中读取字节并直接将这些字节用作数字的字节是实现定义的行为。 C ++标准不保证整数的内部字节顺序。

首先,您需要一个能从文件中读取小端数的函数,并将它们作为本机整数存储在内存中。如果你已经在一台小端机器上,这似乎是多余的,但没有充分的理由不以这种方式阅读,并且有充分的理由不盲目地假设你在小端上运行架构。

接下来,你需要正确地遵循规范并读取大小和偏移来解析信息,正如雷米所说。盲目地假设所有wav文件都是相同的,这是一个比你想象的更糟糕的假设。

就实际问题排查而言,一次读取一个字节的文件并打印出来。确保您实际上正在阅读您期望的数据。有时,奇怪的事情会发生,特别是在Linux上。

答案 1 :(得分:0)

该代码并非即将关闭是读取WAV文件的正确方法。 WAV文件具有结构,但代码完全忽略了该结构,在不验证它们的情况下做出了不准确的假设:

  • 它假设WAVE块中的第一个子块是fmt\0 - 并非总是如此!
  • 它假设fmt\0块数据的大小恰好是16个字节 - 并非总是如此!
  • 它假设fmt\0data块之间不存在其他块 - 并非总是如此!

您确实应该使用预先存在的库来读取音频文件,例如libav,但如果您打算手动执行,至少要注意您正在阅读的内容。每个块都有一个标题,指示块类型和数据大小。你必须正确地考虑这些。在循环中读取块,根据需要读取每个头,数据有效负载和可选填充,检查您感兴趣的特定块,并忽略您不感兴趣的其他块。

尝试更像这样的事情:

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