我正在尝试加载.WAV文件以与OpenAL一起播放。我正在效仿我在互联网上找到的一个例子,但它表现得很奇怪。这是代码:
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;
}
我有几个大约50kb的WAV文件。他们加载和播放就好了。但是,当我尝试加载整首歌曲(是的,我验证文件使用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以下的东西工作(还没有完全测试过,很难找到漂浮在我电脑上的完美尺寸的声音文件在网上)。这只是猜测,我真的不知道发生了什么。感谢帮助!
答案 0 :(得分:1)
这个问题有点老了,但是没有答案。我碰巧写了一个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"
}
// "data" is now properly expected
if (bytes[0] != 'd' && bytes[1] != 'a' && bytes[2] != 't' && bytes[3] != 'a')
return false;
答案 1 :(得分:0)
我知道这个问题已经存在很长时间了,但是我找到了一个教程并对其进行了测试。有用。试试这个:
//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;
}
答案 2 :(得分:-2)
是的,这是最简单的解决方案之一,但仅适用于RIFF文件。代替:
while (true){};
最好插入:
// Wait for the song to complete
ALint state;
do {
alGetSourcei(sourceid, AL_SOURCE_STATE, &state);
} while (state == AL_PLAYING);