正在为OpenAL加载.WAV文件



我正在尝试加载要用OpenAL播放的.WAV文件。我正在效仿我在网上找到的一个例子,但它的行为很奇怪。这是代码:

struct RIFF_Header {
    char chunkID[4];
    long chunkSize;//size not including chunkSize or chunkID
    char format[4];
};
/*
 * Struct to hold fmt subchunk data for WAVE files.
 */
struct WAVE_Format {
    char subChunkID[4];
    long subChunkSize;
    short audioFormat;
    short numChannels;
    long sampleRate;
    long byteRate;
    short blockAlign;
    short bitsPerSample;
};
/*
* Struct to hold the data of the wave file
*/
struct WAVE_Data {
    char subChunkID[4]; //should contain the word data
    long subChunk2Size; //Stores the size of the data block
};
bool loadWavFile(std::string filename, ALuint* buffer,
                 ALsizei* size, ALsizei* frequency,
                 ALenum* format) {
    //Local Declarations
    FILE* soundFile = NULL;
    WAVE_Format wave_format;
    RIFF_Header riff_header;
    WAVE_Data wave_data;
    unsigned char* data;
    *size = wave_data.subChunk2Size;
    *frequency = wave_format.sampleRate;
    if (wave_format.numChannels == 1) {
        if (wave_format.bitsPerSample == 8 )
            *format = AL_FORMAT_MONO8;
        else if (wave_format.bitsPerSample == 16)
            *format = AL_FORMAT_MONO16;
    } else if (wave_format.numChannels == 2) {
        if (wave_format.bitsPerSample == 8 )
            *format = AL_FORMAT_STEREO8;
        else if (wave_format.bitsPerSample == 16)
            *format = AL_FORMAT_STEREO16;
    }
    try {
        soundFile = fopen(filename.c_str(), "rb");
        if (!soundFile)
            throw (filename);
        // Read in the first chunk into the struct
        fread(&riff_header, sizeof(RIFF_Header), 1, soundFile);
        //check for RIFF and WAVE tag in memeory
        if ((riff_header.chunkID[0] != 'R' ||
             riff_header.chunkID[1] != 'I' ||
             riff_header.chunkID[2] != 'F' ||
             riff_header.chunkID[3] != 'F') ||
            (riff_header.format[0] != 'W' ||
             riff_header.format[1] != 'A' ||
             riff_header.format[2] != 'V' ||
             riff_header.format[3] != 'E'))
            throw ("Invalid RIFF or WAVE Header");
        //Read in the 2nd chunk for the wave info
        fread(&wave_format, sizeof(WAVE_Format), 1, soundFile);
        //check for fmt tag in memory
        if (wave_format.subChunkID[0] != 'f' ||
            wave_format.subChunkID[1] != 'm' ||
            wave_format.subChunkID[2] != 't' ||
            wave_format.subChunkID[3] != ' ')
            throw ("Invalid Wave Format");
        //check for extra parameters;
        if (wave_format.subChunkSize > 16)
            fseek(soundFile, sizeof(short), SEEK_CUR);
        //Read in the the last byte of data before the sound file
        fread(&wave_data, sizeof(WAVE_Data), 1, soundFile);
        //check for data tag in memory
        if (wave_data.subChunkID[0] != 'd' ||
            wave_data.subChunkID[1] != 'a' ||
            wave_data.subChunkID[2] != 't' ||
            wave_data.subChunkID[3] != 'a')
            throw ("Invalid data header");
        //Allocate memory for data
        data = new unsigned char[wave_data.subChunk2Size];
        // Read in the sound data into the soundData variable
        if (!fread(data, wave_data.subChunk2Size, 1, soundFile))
            throw ("error loading WAVE data into struct!");
        //Now we set the variables that we passed in with the
        //data from the structs
        *size = wave_data.subChunk2Size;
        *frequency = wave_format.sampleRate;
        //The format is worked out by looking at the number of
        //channels and the bits per sample.
        if (wave_format.numChannels == 1) {
            if (wave_format.bitsPerSample == 8 )
                *format = AL_FORMAT_MONO8;
            else if (wave_format.bitsPerSample == 16)
                *format = AL_FORMAT_MONO16;
        } else if (wave_format.numChannels == 2) {
            if (wave_format.bitsPerSample == 8 )
                *format = AL_FORMAT_STEREO8;
            else if (wave_format.bitsPerSample == 16)
                *format = AL_FORMAT_STEREO16;
        }
        //create our openAL buffer and check for success
        alGenBuffers(2, buffer);
        if(alGetError() != AL_NO_ERROR) {
            std::cerr << alGetError() << std::endl;
        }
        //now we put our data into the openAL buffer and
        //check for success
        alBufferData(*buffer, *format, (void*)data,
                     *size, *frequency);
        if(alGetError() != AL_NO_ERROR) {
            std::cerr << alGetError() << std::endl;
        }
        //clean up and return true if successful
        fclose(soundFile);
        delete data;
        return true;
    } catch(const char* error) {
        //our catch statement for if we throw a string
        std::cerr << error << " : trying to load "
        << filename << std::endl;
        //clean up memory if wave loading fails
        if (soundFile != NULL)
            fclose(soundFile);
        //return false to indicate the failure to load wave
        delete data;
        return false;
    }
}
int main() {
    ALuint buffer, source;
    ALint state;
    ALsizei size;
    ALsizei frequency;
    ALenum format;
    ALCcontext *context;
    ALCdevice *device;
    device = alcOpenDevice(nullptr);
    if (device == NULL)
    {
        cerr << "Error finding default Audio Output Device" << endl;
    }
    context = alcCreateContext(device,NULL);
    alcMakeContextCurrent(context);
    alGetError();
    loadWavFile("test.wav", &buffer, &size, &frequency, &format);
    alGenSources(1, &source);
    alSourcei(source, AL_BUFFER, buffer);
    // Play
    alSourcePlay(source);
    // Wait for the song to complete
    do {
        alGetSourcei(source, AL_SOURCE_STATE, &state);
    } while (state == AL_PLAYING);
    // Clean up sources and buffers
    alDeleteSources(1, &source);
    alDeleteBuffers(1, &buffer);
    return 0;
}

我有几个WAV文件,都在50kb左右。他们的负荷和发挥都很好。然而,当我尝试加载整首歌时(是的,我使用VLC Media Player和MusicBee验证了文件的格式正确),它会返回"无效数据头:尝试加载test.wav",这是由以下块引起的:

if (wave_data.subChunkID[0] != 'd' ||
wave_data.subChunkID[1] != 'a' ||
wave_data.subChunkID[2] != 't' ||
wave_data.subChunkID[3] != 'a')
throw ("Invalid data header");

我怀疑是大小相关的东西导致了这个标题的脱落,因为似乎只有1000kb以下的东西才能工作(还没有完全测试过,很难在我的电脑和互联网上找到完美大小的声音文件)。这只是一个猜测,我真的不知道发生了什么。谢谢你的帮助!

我知道这个问题已经存在很长时间了,但我找到了一个教程并进行了测试。它很有效。试试这个:

//http://www.youtube.com/user/thecplusplusguy
//Playing 3D sound with OpenAL, and loading a wav file manually
#include <iostream>
#include <fstream>
#include <cstring>
#include <al.h>
#include <alc.h>
bool isBigEndian()
{
    int a = 1;
    return !((char*)&a)[0];
}
int convertToInt(char* buffer, int len)
{
    int a = 0;
    if (!isBigEndian())
        for (int i = 0; i<len; i++)
            ((char*)&a)[i] = buffer[i];
    else
        for (int i = 0; i<len; i++)
            ((char*)&a)[3 - i] = buffer[i];
    return a;
}
char* loadWAV(const char* fn, int& chan, int& samplerate, int& bps, int& size)
{
    char buffer[4];
    std::ifstream in(fn, std::ios::binary);
    in.read(buffer, 4);
    if (strncmp(buffer, "RIFF", 4) != 0)
    {
        std::cout << "this is not a valid WAVE file" << std::endl;
        return NULL;
    }
    in.read(buffer, 4);
    in.read(buffer, 4);      //WAVE
    in.read(buffer, 4);      //fmt
    in.read(buffer, 4);      //16
    in.read(buffer, 2);      //1
    in.read(buffer, 2);
    chan = convertToInt(buffer, 2);
    in.read(buffer, 4);
    samplerate = convertToInt(buffer, 4);
    in.read(buffer, 4);
    in.read(buffer, 2);
    in.read(buffer, 2);
    bps = convertToInt(buffer, 2);
    in.read(buffer, 4);      //data
    in.read(buffer, 4);
    size = convertToInt(buffer, 4);
    char* data = new char[size];
    in.read(data, size);
    return data;
}
int main(int argc, char** argv)
{
    int channel, sampleRate, bps, size;
    char* data = loadWAV("C:/Users/Gizego/Desktop/Youtube/Músicas/TheFatRat+-+Time+Lapse.wav", channel, sampleRate, bps, size);
    ALCdevice* device = alcOpenDevice(NULL);
    if (device == NULL)
    {
        std::cout << "cannot open sound card" << std::endl;
        return 0;
    }
    ALCcontext* context = alcCreateContext(device, NULL);
    if (context == NULL)
    {
        std::cout << "cannot open context" << std::endl;
        return 0;
    }
    alcMakeContextCurrent(context);
    unsigned int bufferid, format;
    alGenBuffers(1, &bufferid);
    if (channel == 1)
    {
        if (bps == 8)
        {
            format = AL_FORMAT_MONO8;
        }
        else {
            format = AL_FORMAT_MONO16;
        }
    }
    else {
        if (bps == 8)
        {
            format = AL_FORMAT_STEREO8;
        }
        else {
            format = AL_FORMAT_STEREO16;
        }
    }
    alBufferData(bufferid, format, data, size, sampleRate);
    unsigned int sourceid;
    alGenSources(1, &sourceid);
    alSourcei(sourceid, AL_BUFFER, bufferid);
    alSourcePlay(sourceid);
    while (true)
    {
    }
    alDeleteSources(1, &sourceid);
    alDeleteBuffers(1, &bufferid);
    alcDestroyContext(context);
    alcCloseDevice(device);
    delete[] data;
    return 0;
}

这个问题有点老,但没有答案。我碰巧写了一个WAV文件加载程序,我偶然发现了与您完全相同的问题。

事实上;数据";部分不能保证存在于你期望的地方。还有其他可以指定的块,比如在我的情况下的";提示";一这是一种非常隐蔽的信息,我花了很多时间试图找到它:https://sites.google.com/site/musicgapi/technical-documents/wav-file-format#cue

在我的情况下,我然后简单地检查是否存在";提示";部分,并简单地忽略其数据。我(还)没有检查其他块类型,因为我没有任何测试材料。

在C++代码中,这就完成了任务:

// std::ifstream file("...", std::ios_base::in | std::ios_base::binary);
// std::array<uint8_t, 4> bytes {};
file.read(reinterpret_cast<char*>(bytes.data()), 4); // Supposed to be "data"
// A "cue " field can be specified; if so, the given amount of bytes will be ignored
if (bytes[0] == 'c' && bytes[1] == 'u' && bytes[2] == 'e' && bytes[3] == ' ') {
  file.read(reinterpret_cast<char*>(bytes.data()), 4); // Cue data size
  const uint32_t cueDataSize = fromLittleEndian(bytes);
  file.ignore(cueDataSize);
  file.read(reinterpret_cast<char*>(bytes.data()), 4); // "data"
}
// A LIST segment may be specified; see the edit below
// "data" is now properly expected
if (bytes[0] != 'd' && bytes[1] != 'a' && bytes[2] != 't' && bytes[3] != 'a')
  return false;

编辑:我现在有一个"LIST";标签,这是意料之中的。这可以像提示一样被忽略:

if (bytes[0] == 'L' && bytes[1] == 'I' && bytes[2] == 'S' && bytes[3] == 'T') {
  file.read(reinterpret_cast<char*>(bytes.data()), 4); // List data size
  const uint32_t listDataSize = fromLittleEndian(bytes);
  file.ignore(listDataSize);
  file.read(reinterpret_cast<char*>(bytes.data()), 4);
}
是的,这是最简单的解决方案之一,但仅适用于RIFF文件。相反:while (true){};

最好插入:// Wait for the song to completeALint state;do {alGetSourcei(sourceid, AL_SOURCE_STATE, &state);} while (state == AL_PLAYING);

最新更新