Advertisement

OpenAL source stopped, but no buffers seem to be processed

Started by April 26, 2020 04:55 PM
2 comments, last by DrDeath3191 4 years, 7 months ago

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, &currentState);

		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) {
}

Alright, progress report! Well, more like a lack of progress; I still haven't fixed the issue. But I have learned a couple of things;

  1. The issue does not occur when running in Release mode; this is a Debug issue only.
  2. It is NOT code related: I created a separate Visual Studio project to see if I could break it in the same way; no dice. The code is identical, but the test project works just fine, while the main project crashes.
  3. I don't think it's a library issue; I went through and added every external library from my game one by one into the test project; the test project still runs fine.

I must admit I'm at a bit of a loss as to what I could possibly have left to check. I think the compiler flags for the test project and the main one are the same (I went through the project properties to compare and change), but for some reason one works while the other doesn't. I'm really quite confused.

I also came up with a simpler bit of code to cause the crash; no GameState required for this to crash, I can do it from main, even without a second thread:

#include "SoundPlayer.h"
#include "WavLoader.h"

int main(int argc, char** argv) {

	SoundPlayer soundPlayer;

	soundPlayer.init();

	WavLoader loader;

	auto soundData = loader.loadWAV("Path\\To\\Sound.wav");

	uint32_t sourceID, bufferID;

	alGenSources(1, &sourceID);
	alGenBuffers(1, &bufferID);

	auto format = AL_FORMAT_MONO16;
	int sampleRate = 44100;
	int dataSize = 21282;

	alBufferData(bufferID, format, soundData, dataSize, sampleRate);

	SoundEmitter emitter;

	soundPlayer.playSound(emitter, bufferID, false, Priority::HIGHEST);

	while (true) { soundPlayer.update(); }

}

Once again, the above code works just fine in my test project, but causes my main project to crash in Debug mode. I'm at a complete loss as to where to even look for the problem at this point, so any guidance you could give would be greatly appreciated.

Advertisement

I FIXED THE PROBLEM, AND IT WAS SUPER DUMB.

So my latest idea was to literally copy and paste my project to another folder and remove all unneeded elements. It of course crashed again as expected, but this time Visual Studio threw me a bone. I got a warning saying that the intermediary directory x64/Debug had some shared files from the original project. That's fine, I just deleted the directory and rebuilt the project. Went off without a hitch. Flabbergasted, I tried it on the original. Worked perfectly. I am a little bit upset that something so simple was the answer.

I guess what happened is, since I am building mostly in Debug 64-bit mode, the dlls and such (inserted automatically as a build step) I had were from the old OpenAL libraries I had, and that screwed up the build. I had cleaned and rebuilt prior, so I thought those files would have been deleted in the process. Apparently not!

So the lesson here, children, is that if you actually want a clean rebuild be sure to delete the old build directories.

This topic is closed to new replies.

Advertisement