Hello Everyone,
I haven't touched my audio related code in a while, since it worked for me. However, I was using the Creative OpenAL libraries rather than OpenALSoft, and wanted to upgrade. So I linked these libraries, and everything compiles just fine. However, if in my current system I play a sound effect, the game crashes.
The issue occurs when I try to unqueue the buffer; when an OpenAL source is stopped, either by reaching the end or forcibly, the buffers should all be marked as processed and therefore be allowed to be unqueued. However my investigations showed that the buffer is never marked as processed, therefore when I try to clean up the source, I crash with an INVALID_VALUE message from OpenAL.
Playing a sound effect with normal OpenAL calls seems to work without issue. Furthermore, this issue does not seem to be present at all in the 32-bit version of OpenALSoft 1.20.1. It only occurs in the 64-bit version.
If I were a betting man, I would wager this is some sort of synchronization issue that I'm just not seeing. But I've been tinkering with the code for a while and getting nowhere, so I would really appreciate the help.
Below is the code I believe to be related to the issue, simplified to show only that which seems relevant:
//SoundPlayer.h
#ifndef SOUND_PLAYER_H
#define SOUND_PLAYER_H
#include <array>
#include <vector>
#include <memory>
#include <mutex>
#include "ISoundStream.h"
#include "SoundEmitter.h"
#include "Priority.h"
const size_t MAX_SOURCES = 32;
class SoundPlayer {
public:
void init();
void update();
void playSound(SoundEmitter& emitter, unsigned int bufferID, bool looping, Priority priotity);
private:
void checkForErrors();
size_t getNextSourceIndex(Priority priority);
bool isSoundEmitterCurrent(SoundEmitter& emitter);
void clearSource(unsigned int sourceID);
std::array<uint32_t, MAX_SOURCES> sourceIDs;
std::array<uint32_t, MAX_SOURCES> sourcePreviousStates;
std::vector<uint32_t> sourcesToClear;
ALCdevice *audioDevice;
ALCcontext *audioContext;
std::mutex mutex;
};
#endif
//SoundPlayer.cpp
#include <algorithm>
#include <al.h>
#include <alc.h>
#include "SoundPlayer.h"
void SoundPlayer::init() {
audioDevice = alcOpenDevice(NULL);
audioContext = alcCreateContext(audioDevice, NULL);
alcMakeContextCurrent(audioContext);
alcProcessContext(audioContext);
alGetError(); //clear error buffer beforehand
alGenSources(MAX_SOURCES, &sourceIDs[0]);
std::fill(sourcePreviousStates.begin(), sourcePreviousStates.end(), AL_INITIAL);
checkForErrors();
}
void SoundPlayer::release() {
checkForErrors();
ALCcontext *context = alcGetCurrentContext();
ALCdevice *device = alcGetContextsDevice(context);
stopAllSounds();
alDeleteBuffers(MAX_SOURCES * 2, &streamBuffers[0]);
alDeleteSources(MAX_SOURCES, &sourceIDs[0]);
checkForErrors();
alcMakeContextCurrent(NULL);
alcDestroyContext(context);
alcCloseDevice(device);
}
void SoundPlayer::stopAllSounds() {
std::unique_lock<std::mutex> lock(mutex);
//stop sources and unqueue all buffers
for (size_t i = 0; i < MAX_SOURCES; i++) {
alSourceStop(sourceIDs[i]);
clearSource(sourceIDs[i]);
}
}
size_t SoundPlayer::getNextSourceIndex(Priority priority) {
//the actual method to determine this is slightly more complex, and irrelevant to this issue
return 0;
}
//ERROR OCCURS HERE
void SoundPlayer::clearSource(unsigned int sourceID) {
checkForErrors();
int queued;
alGetSourcei(sourceID, AL_BUFFERS_QUEUED, &queued);
while (queued) {
unsigned int bufferID = 0;
alSourceUnqueueBuffers(sourceID, 1, &bufferID);
queued--;
}
checkForErrors();
}
void SoundPlayer::playSound(SoundEmitter& emitter, unsigned int bufferID, bool looping, Priority priority) {
std::unique_lock<std::mutex> lock(mutex);
size_t index = getNextSourceIndex(priority);
if (index == MAX_SOURCES) {
return;
}
unsigned int nextSource = sourceIDs[index];
emitter.sourceIndex = index;
alSourcei(nextSource, AL_BUFFER, bufferID);
//set source parameters
//Not shown, as it is irrelevant to the issue
//play the sound
alSourcePlay(nextSource);
//if this source is in the clear list, remove it
sourcesToClear.erase(std::remove(sourcesToClear.begin(), sourcesToClear.end(), nextSource), sourcesToClear.end());
checkForErrors();
}
//This is spun in a separate thread; usually used to handle streams, but those work fine currently, so code related to them has been removed. The call to clearSource that crashes the game occurs here.
void SoundPlayer::update() {
for (size_t i = 0; i < MAX_SOURCES; i++) {
ALenum currentState;
unsigned int currentID = sourceIDs[i];
alGetSourcei(currentID, AL_SOURCE_STATE, ¤tState);
if (currentState == AL_INITIAL) {
continue;
}
unsigned int previousState = sourcePreviousStates[i];
sourcePreviousStates[i] = currentState;
if (currentState == AL_STOPPED && previousState != currentState) {
std::unique_lock<std::mutex> lock(mutex);
sourcesToClear.emplace_back(currentID);
continue;
}
}
std::unique_lock<std::mutex> lock(mutex);
for (size_t i = 0; i < sourcesToClear.size(); i++) {
clearSource(sourcesToClear[i]);
}
sourcesToClear.clear();
}
void SoundPlayer::checkForErrors() {
int error = alGetError();
if (error != AL_NO_ERROR) {
switch(error) {
case AL_INVALID_NAME: {
printf("INVALID NAME\n");
break;
} case AL_INVALID_ENUM: {
printf("INVALID ENUM\n");
break;
} case AL_INVALID_VALUE: {
printf("INVALID VALUE\n");
break;
} case AL_ILLEGAL_COMMAND: {
printf("ILLEGAL COMMAND\n");
break;
} case AL_OUT_OF_MEMORY: {
printf("OUT OF MEMORY\n");
break;
}
}
throw std::exception("OpenAL error");
}
}
//AudioTestState.h
//This is a GameState created solely to debug this issue
#ifndef AUDIO_TEST_STATE_H
#define AUDIO_TEST_STATE_H
#include <algorithm>
#include <iostream>
#include <glm.hpp>
#include "SoundAsset.h"
#include "Game.h"
#include "SoundEmitter.h"
class AudioTestState : public GameState {
public:
AudioTestState(Game& game);
~AudioTestState();
void render(float alpha);
void enter();
void exit();
void handleInput(std::vector<InputEvent> input);
void update(GameTime time);
private:
//A SoundEmitter is simply a POD struct with all the same variables as an OpenAL source, which makes it easier to set those values
SoundEmitter soundEmitter;
ALuint bufferID;
};
#endif
//AudioTestState.cpp
#include "AudioTestState.h"
AudioTestState::AudioTestState(Game& game) : GameState(game) {
WavLoader loader;
auto soundData = loader.loadWAV("Path\\To\\File.wav");
alGenSources(1, &sourceID);
alGenBuffers(1, &bufferID);
auto format = AL_FORMAT_MONO16;
int sampleRate = 44100;
int dataSize = 21282;
alBufferData(bufferID, format, soundData, dataSize, sampleRate);
}
AudioTestState::~AudioTestState() {
}
void AudioTestState::render(float alpha) {
}
void AudioTestState::handleInput(std::vector<InputEvent> input) {
for (size_t i = 0; i < input.size(); i++) {
switch (input[i].key.type) {
case QUIT:
game.shutDown();
break;
case KEYBOARD_SCANCODE:
//if we press down the P key, play the sound
if (input[i].key.inputID == SDL_SCANCODE_P && input[i].value == 1) {
game.getSoundPlayer().playSound(soundEmitter, bufferID, false, Priority::HIGH);
} else if (input[i].key.inputID == SDL_SCANCODE_ESCAPE && input[i].value == 1) {
game.shutDown();
break;
}
break;
}
}
}
void AudioTestState::enter() {
}
void AudioTestState::exit() {
}
void AudioTestState::update(GameTime time) {
}