首先,我想说我已经读过另一个类似的问题,但是在这些问题中我找不到解决方案。
我正在使用“ 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;
}
}
很抱歉,一些变量名和注释是西班牙语的,但我认为这很容易理解。
我假设最后2个元素仅根据“ AudioFormat”字段值出现。如果等于1,则不会显示这些元素。否则,它们可能会或可能不会出现。知道这一点,我正在比较“ FmtChunkSize”字段值:
然后,我正在寻找“数据”字符串。当我最终找到它时,我读取了它的大小(接下来的4个字节)并创建了该大小的缓冲区。从alGenBuffers(1, audiobuffer->buffer);
开始,我只是创建OpenAL缓冲区(这不是我的问题所在)。
调试时,我发现AudioFormat的值为1(因此它不应具有最后两个字段),但FmtChunkSize大于16(因此它应具有后两个字段...有点冲突...)因此,我可能会认为我的问题是我没有考虑字节顺序,但是如果是这样,我不知道如何正确读取值。
我正在加载的WAV文件是可以的,因为我认识的其他人已经使用其代码成功播放了该文件。
对不起,如果我对自己的解释不够好,并且对问题的大小也感到抱歉,但我认为这可能对您了解我要遵循的WAV标头结构很有帮助。
任何帮助将不胜感激,非常感谢。
答案 0 :(得分:0)
wav文件的工作方式与您认为的有所不同。
您有数据块,每个数据块必须一起读取。它们都遵循相同的模式:4个字符,4个字节的大小以及可能的其他数据。
第一个块应该是固定的,即文件类型,然后是文件的大小(-8个字节,因此是文件的剩余大小)和文件格式。
然后,有通常的(可能是)块。您有一个类型(4个字符),然后是块的大小(-8个字节)和相关数据。
在您的情况下,第二个块似乎是“ fmt”。您只需要关心此块的大小即可知道您是否拥有超过16个字节的信息。这就是决定事情的原因。
然后您拥有“数据”块。相同的模式,四个字符,然后是声音数据的大小和数据本身。
但是您可以获得其他块,例如“ bext”,因此您需要读取所有其他块,而不仅仅是“数据”。
正如我所说,它们遵循相同的模式。 4个字符,大小为4个字节,然后附加到该块的一些字节(大小为size
)。如果您遵守规则,那么您将能够读取文件。