【问题标题】:How to properly read a WAV header in C++?如何在 C++ 中正确读取 WAV 标头?
【发布时间】:2019-02-03 14:56:02
【问题描述】:

首先,让我说我一直在阅读其他类似的问题,但我无法在其中找到解决问题的方法。

我正在使用“OpenAL”库通过创建一个 AudioBuffer 和一个 AudioSource 来播放 WAV 文件,但我认为这无关紧要。我创建了一个名为 AudioBuffer 的类,它有一个静态方法来获取所有信息,然后返回一个指向在其中创建的对象的指针。我要做的是读取 WAV 文件。为此,我首先读取标题以获取每个字段的值,然后使用我之前读取的“数据大小”构建一个缓冲区并将整个数据字段存储在其中。问题是当我尝试加载 WAV 文件时,它就无法播放。这是我用来加载 WAV 文件和读取其字段的函数:

typedef struct {
char chunk_id[4];
uint32_t chunk_size;
char format[4];
} wave_header;

typedef struct {
char id[4];
uint32_t size;
} riff_chunk_header;

typedef struct {
uint16_t audio_format;
uint16_t num_channels;
uint32_t sample_rate;
uint32_t byte_rate;
uint16_t block_align;
uint16_t bits_per_sample;
} wave_fmt_chunk;

AudioBuffer* AudioBuffer::load(const char* filename) {

wave_header w_header;
riff_chunk_header r_c_header;
wave_fmt_chunk w_f_chunk;
short extra_params_size = 0;
bool data = false;
char bloque[1];
int data_size = 0;

AudioBuffer *audiobuffer = new AudioBuffer(1);

std::ifstream in(filename, std::ios::binary);

if (in.is_open()) {

    printf("Fichero abierto correctamente.\n");

    in.read(w_header.chunk_id, 4);

    if (strncmp(w_header.chunk_id, "RIFF", 4) != 0) {
        printf("El fichero no es de tipo WAV.\n");
        return nullptr;
    }
    else {
        printf("Fichero WAV valido.\n");
    }

    in.read(reinterpret_cast<char *>(&w_header.chunk_size), 4);
    in.read(w_header.format, 4);

    in.read(r_c_header.id, 4);
    in.read(reinterpret_cast<char *>(&r_c_header.size), 4); //FmtChunkSize

    in.read(reinterpret_cast<char *>(&w_f_chunk.audio_format), 2);
    in.read(reinterpret_cast<char *>(&w_f_chunk.num_channels), 2);
    in.read(reinterpret_cast<char *>(&w_f_chunk.sample_rate), 4);
    in.read(reinterpret_cast<char *>(&w_f_chunk.byte_rate), 4);
    in.read(reinterpret_cast<char *>(&w_f_chunk.block_align), 2);
    in.read(reinterpret_cast<char *>(&w_f_chunk.bits_per_sample), 2);

    if (r_c_header.size > 16) {
        in.read(reinterpret_cast<char *>(&extra_params_size), 2);
        in.ignore(extra_params_size); //Ignoramos los bytes de parámetros adicionales.
    }

    while (!data) {
        in.read(bloque, 1);
        if (bloque[0] == 'd') {
            in.read(bloque, 1);
            if (bloque[0] == 'a') {
                in.read(bloque, 1);
                if (bloque[0] == 't') {
                    in.read(bloque, 1);
                    if (bloque[0] == 'a')
                        data = true; //Se ha encontrado "data".
                }
            }
        }

    }

    //Una vez encontrado "data"
    in.read(reinterpret_cast<char *>(&data_size), 4); //Leemos el tamaño del bloque data.

    char *m_data = new char[data_size]; //Buffer con el tamaño de los datos.
    in.read(m_data, data_size); //Rellenamos el buffer con los datos.

    //Generamos el buffer de OpenAL.
    alGenBuffers(1, audiobuffer->buffer);

    if (w_f_chunk.bits_per_sample == 8) {
        if (w_f_chunk.num_channels == 1) {
            alBufferData(audiobuffer->buffer[0], AL_FORMAT_MONO8, m_data, data_size, w_f_chunk.sample_rate);
        }
        else {
            alBufferData(audiobuffer->buffer[0], AL_FORMAT_STEREO8, m_data, data_size, w_f_chunk.sample_rate);
        }
    }
    else if (w_f_chunk.bits_per_sample == 16) {
        if (w_f_chunk.num_channels == 1) {
            alBufferData(audiobuffer->buffer[0], AL_FORMAT_MONO16, m_data, data_size, w_f_chunk.sample_rate);
        }
        else {
            alBufferData(audiobuffer->buffer[0], AL_FORMAT_STEREO16, m_data, data_size, w_f_chunk.sample_rate);
        }
    }

    return audiobuffer;
}
else {
    printf("El fichero no se pudo abrir. Ruta incorrecta.\n");
    return nullptr;
}
}

抱歉,有些变量名和 cmets 是西班牙语,但我认为这很容易理解。

  • 首先我打开函数通过参数获取的文件,如果打开成功就打印出来。
  • 然后我查找“RIFF”字符串,它告诉我它是否是有效的 WAV 文件。
  • 之后,我读取了每个字段的值。

我关注的 WAV 标头结构是这样的:

我假设最后 2 个元素仅根据“AudioFormat”字段值出现。如果它等于 1,则不会出现这些元素。否则,它们可能会出现,也可能不会出现。为了知道这一点,我正在比较“FmtChunkSize”字段值:

  • 如果等于 16,那么最后 2 个字段肯定不存在。
  • 如果大于 16,那么我必须读取“ExtraParamsSize”字段的值并在读取时跳过这些字节。

然后,我正在寻找“数据”字符串。当我终于找到它时,我读取了它的大小(接下来的 4 个字节)并创建了一个该大小的缓冲区。从alGenBuffers(1, audiobuffer-&gt;buffer); 开始,我只是在创建 OpenAL 缓冲区(这不是我的问题所在)。

调试我发现AudioFormat值为1(所以它不应该有最后两个字段)但FmtChunkSize大于16(所以它应该有最后两个字段......有点冲突......)因此,我可能认为我的问题是我没有考虑字节序,但如果是这样,我不知道如何正确读取这些值。

我正在加载的 WAV 文件没有问题,因为我认识的其他人已经使用他们的代码成功播放了它。

对不起,如果我没有很好地解释自己,也很抱歉问题的大小,但我认为了解我所遵循的 WAV 标头结构可能会对您有所帮助。

任何帮助将不胜感激,非常感谢您。

【问题讨论】:

  • 无法播放是什么意思?您确定在您阅读的那个之后没有另一个(非数据)块吗?

标签: c++ audio wav


【解决方案1】:

wav 文件的工作方式与您想象的略有不同。

您有大量数据,必须一起读取每个数据。它们都遵循相同的模式:4 个字符、4 个字节的大小以及可能的附加数据。

第一个块应该是固定的,即文件类型,然后是文件的大小(-8 字节,所以这是文件的剩余大小)和文件格式。

然后,有通常的(可能)块。你有一个类型(4 个字符),然后是块的大小(-8 个字节)和相关数据。

在您的情况下,第二个块似乎是“fmt”。你应该只关心这个块的大小来知道你是否有超过 16 字节的信息。这就是决定事情的因素。

然后你就有了“数据”块。相同的模式,4 个字符,然后是声音数据的大小和数据本身。

但是你可以得到其他块,比如“bext”,所以你需要读取所有其他块,而不仅仅是“数据”。

正如我所说,它们遵循相同的模式。 4 个字符,4 个字节的大小,然后是附加到该块的一些字节(大小为 size)。如果您遵守规则,那么您将能够读取您的文件。

【讨论】:

    猜你喜欢
    • 2016-04-29
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-16
    • 1970-01-01
    • 2012-03-23
    • 2012-05-08
    • 1970-01-01
    相关资源
    最近更新 更多