changeset 12179:ce025491d1e5 draft

Implement OpenAL sound driver.
author Michael Lutz <michi@icosahedron.de>
date Thu, 12 Jun 2008 23:58:52 +0200
parents 08d76f3dda8b
children 71a198100dd0
files src/sound/openal_s.cpp
diffstat 1 files changed, 98 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- a/src/sound/openal_s.cpp
+++ b/src/sound/openal_s.cpp
@@ -6,17 +6,115 @@
 
 #include "../driver.h"
 #include "../mixer.h"
+#include "../thread.h"
+#include "../gfx_func.h"
 #include "openal_s.h"
+#include <AL/al.h>
+#include <AL/alc.h>
+
+#define NUM_BUFFERS 3
 
 static FSoundDriver_OpenAL iFSoundDriver_OpenAL;
 
+static ALCdevice *_al_device;
+static ALCcontext *_al_context;
+
+static ALuint _buffers[NUM_BUFFERS];
+static ALuint _source;
+
+static int _buf_size;
+
+static ThreadObject *_al_thread = NULL;
+static volatile bool _al_running;    ///< Simple semaphore signalling the thread when to stop
+
+void * CDECL OAL_ThreadFunc(void *)
+{
+	void *data = alloca(_buf_size);
+
+	while (_al_running) {
+		ALint buffers_processed = 0;
+		alGetSourcei(_source, AL_BUFFERS_PROCESSED, &buffers_processed);
+		for (; buffers_processed > 0; --buffers_processed) {
+			ALuint buffer = 0;
+			alSourceUnqueueBuffers(_source, 1, &buffer);
+			/* Note: MxMixSamples is not completely thread-safe, but it's good enough
+			 * to be usable. As mixer channels are only stopped by MxMixSamples itself,
+			 * we'll never have the problem that the sound data is going to vanish during
+			 * use. The worst that could happen is that a newly allocated channel is
+			 * missed, but as it will get picked up on the next buffer fill, this is not
+			 * critical in any way. */
+			MxMixSamples(data, _buf_size / 4);
+			alBufferData(buffer, AL_FORMAT_STEREO16, data, _buf_size, 11025);
+			alSourceQueueBuffers(_source, 1, &buffer);
+		}
+
+		ALint state;
+		alGetSourcei(_source, AL_SOURCE_STATE, &state);
+		if (state != AL_PLAYING) {
+			/* The source ran out of buffers, restart playing. */
+			alSourcePlay(_source);
+		}
+
+		/* Try to sleep as long as it takes to play a buffer. */
+		CSleep(_buf_size / 4 * 1000 / 11025);
+	}
+	return NULL;
+}
+
 const char *SoundDriver_OpenAL::Start(const char * const *parm)
 {
+	/* Open device */
+	_al_device = alcOpenDevice(NULL);
+	if (!_al_device) return "alcOpenDevice failed";
+
+	/* Create context */
+	ALCint attribs[] = {ALC_STEREO_SOURCES, 0, ALC_FREQUENCY, 11025};
+	attribs[3] = GetDriverParamInt(parm, "hz", 11025);
+
+	_al_context = alcCreateContext(_al_device, attribs);
+	if (!_al_context) return "alcCreateContext failed";
+	alcMakeContextCurrent(_al_context);
+
+	/* Prepare buffers */
+	alGenBuffers(NUM_BUFFERS, _buffers);
+	alGenSources(1, &_source);
+	if (alGetError() != AL_NO_ERROR) return "Can't create buffers";
+
+	/* Buffer size has to be a multiple of 4 */
+	_buf_size = GetDriverParamInt(parm, "bufsize", 1024);
+	_buf_size &= ~3U;
+
+	/* Fill all buffers initially */
+	void *data = alloca(_buf_size);
+	for (int i = 0; i < NUM_BUFFERS; ++i) {
+		MxMixSamples(data, _buf_size / 4);
+		alBufferData(_buffers[i], AL_FORMAT_STEREO16, data, _buf_size, 11025);
+		alSourceQueueBuffers(_source, 1, &_buffers[i]);
+	}
+	alSourcePlay(_source);
+
+	_al_running = true;
+	_al_thread = ThreadObject::New(&OAL_ThreadFunc, NULL);
+	_al_thread->SetThreadPriority(ThreadObject::THREAD_PRIO_HIGH);
+
 	return NULL;
 }
 
 void SoundDriver_OpenAL::Stop()
 {
+	_al_running = false;
+	if (_al_thread != NULL) {
+		_al_thread->Join();
+		delete _al_thread;
+		_al_thread = NULL;
+	}
+
+	alDeleteSources(1, &_source);
+	alDeleteBuffers(NUM_BUFFERS, _buffers);
+
+	alcMakeContextCurrent(NULL);
+	alcDestroyContext(_al_context);
+	alcCloseDevice(_al_device);
 }
 
 #endif /* WITH_OPENAL */