From 6c5c58e05799f2b593cd81fcff57e6ef72ad57fb Mon Sep 17 00:00:00 2001 From: Joerg Mueller Date: Sun, 9 Aug 2009 21:16:39 +0000 Subject: 2.5: Sound branch merge! See mailing list for additional information. --- intern/audaspace/OpenAL/AUD_OpenALDevice.cpp | 1362 ++++++++++++++++++++++++++ intern/audaspace/OpenAL/AUD_OpenALDevice.h | 171 ++++ 2 files changed, 1533 insertions(+) create mode 100644 intern/audaspace/OpenAL/AUD_OpenALDevice.cpp create mode 100644 intern/audaspace/OpenAL/AUD_OpenALDevice.h (limited to 'intern/audaspace/OpenAL') diff --git a/intern/audaspace/OpenAL/AUD_OpenALDevice.cpp b/intern/audaspace/OpenAL/AUD_OpenALDevice.cpp new file mode 100644 index 00000000000..f94b11a11b8 --- /dev/null +++ b/intern/audaspace/OpenAL/AUD_OpenALDevice.cpp @@ -0,0 +1,1362 @@ +/* + * $Id$ + * + * ***** BEGIN LGPL LICENSE BLOCK ***** + * + * Copyright 2009 Jörg Hermann Müller + * + * This file is part of AudaSpace. + * + * AudaSpace is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AudaSpace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with AudaSpace. If not, see . + * + * ***** END LGPL LICENSE BLOCK ***** + */ + +#include "AUD_OpenALDevice.h" +#include "AUD_IReader.h" +#include "AUD_IMixer.h" +#include "AUD_ConverterFactory.h" +#include "AUD_SourceCaps.h" + +#include +#include + +#ifdef WIN32 +#include +#else +#include +#endif + +#define AUD_OPENAL_CYCLE_BUFFERS 3 + +/// Saves the data for playback. +struct AUD_OpenALHandle : AUD_Handle +{ + /// Whether it's a buffered or a streamed source. + bool isBuffered; + + /// The reader source. + AUD_IReader* reader; + + /// Whether to keep the source if end of it is reached. + bool keep; + + /// OpenAL sample format. + ALenum format; + + /// OpenAL source. + ALuint source; + + /// OpenAL buffers. + ALuint buffers[AUD_OPENAL_CYCLE_BUFFERS]; + + /// The first buffer to be read next. + int current; + + /// Whether the stream doesn't return any more data. + bool data_end; +}; + +struct AUD_OpenALBufferedFactory +{ + /// The factory. + AUD_IFactory* factory; + + /// The OpenAL buffer. + ALuint buffer; +}; + +typedef std::list::iterator AUD_HandleIterator; +typedef std::list::iterator AUD_BFIterator; + +/******************************************************************************/ +/**************************** Threading Code **********************************/ +/******************************************************************************/ + +void* AUD_openalRunThread(void* device) +{ + AUD_OpenALDevice* dev = (AUD_OpenALDevice*)device; + dev->updateStreams(); + return NULL; +} + +void AUD_OpenALDevice::start() +{ + lock(); + + if(!m_playing) + { + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + pthread_create(&m_thread, &attr, AUD_openalRunThread, this); + + pthread_attr_destroy(&attr); + + m_playing = true; + } + + unlock(); +} + +void AUD_OpenALDevice::updateStreams() +{ + AUD_OpenALHandle* sound; + + int length; + sample_t* buffer; + + ALint info; + AUD_Specs specs; + + while(1) + { + lock(); + + alcSuspendContext(m_context); + + // for all sounds + AUD_HandleIterator it = m_playingSounds->begin(); + while(it != m_playingSounds->end()) + { + sound = *it; + // increment the iterator to make sure it's valid, + // in case the sound gets deleted after stopping + ++it; + + // is it a streamed sound? + if(!sound->isBuffered) + { + // check for buffer refilling + alGetSourcei(sound->source, AL_BUFFERS_PROCESSED, &info); + + if(info) + { + specs = sound->reader->getSpecs(); + + // for all empty buffers + while(info--) + { + // if there's still data to play back + if(!sound->data_end) + { + // read data + length = m_buffersize; + sound->reader->read(length, buffer); + + // read nothing? + if(length == 0) + { + sound->data_end = true; + break; + } + + // unqueue buffer + alSourceUnqueueBuffers(sound->source, 1, + &sound->buffers[sound->current]); + ALenum err; + if((err = alGetError()) != AL_NO_ERROR) + { + sound->data_end = true; + break; + } + + // fill with new data + alBufferData(sound->buffers[sound->current], + sound->format, + buffer, + length * AUD_SAMPLE_SIZE(specs), + specs.rate); + + if(alGetError() != AL_NO_ERROR) + { + sound->data_end = true; + break; + } + + // and queue again + alSourceQueueBuffers(sound->source, 1, + &sound->buffers[sound->current]); + if(alGetError() != AL_NO_ERROR) + { + sound->data_end = true; + break; + } + + sound->current = (sound->current+1) % + AUD_OPENAL_CYCLE_BUFFERS; + } + else + break; + } + } + } + + // check if the sound has been stopped + alGetSourcei(sound->source, AL_SOURCE_STATE, &info); + + if(info != AL_PLAYING) + { + // if it really stopped + if(sound->data_end) + { + // pause or + if(sound->keep) + pause(sound); + // stop + else + stop(sound); + } + // continue playing + else + alSourcePlay(sound->source); + } + } + + alcProcessContext(m_context); + + // stop thread + if(m_playingSounds->empty()) + { + unlock(); + m_playing = false; + pthread_exit(NULL); + } + + unlock(); + +#ifdef WIN32 + Sleep(20); +#else + usleep(20000); +#endif + } +} + +/******************************************************************************/ +/**************************** IDevice Code ************************************/ +/******************************************************************************/ + +bool AUD_OpenALDevice::isValid(AUD_Handle* handle) +{ + for(AUD_HandleIterator i = m_playingSounds->begin(); + i != m_playingSounds->end(); i++) + if(*i == handle) + return true; + for(AUD_HandleIterator i = m_pausedSounds->begin(); + i != m_pausedSounds->end(); i++) + if(*i == handle) + return true; + return false; +} + +AUD_OpenALDevice::AUD_OpenALDevice(AUD_Specs specs, int buffersize) +{ + // cannot determine how many channels or which format OpenAL uses, but + // it at least is able to play 16 bit stereo audio + specs.channels = AUD_CHANNELS_STEREO; + specs.format = AUD_FORMAT_S16; + + m_device = alcOpenDevice(NULL); + + if(!m_device) + AUD_THROW(AUD_ERROR_OPENAL); + + // at least try to set the frequency + ALCint attribs[] = { ALC_FREQUENCY, specs.rate, 0 }; + ALCint* attributes = attribs; + if(specs.rate == AUD_RATE_INVALID) + attributes = NULL; + + m_context = alcCreateContext(m_device, attributes); + alcMakeContextCurrent(m_context); + + alcGetIntegerv(m_device, ALC_FREQUENCY, 1, (ALCint*)&specs.rate); + + // check for specific formats and channel counts to be played back + if(alIsExtensionPresent("AL_EXT_FLOAT32") == AL_TRUE) + specs.format = AUD_FORMAT_FLOAT32; + + m_useMC = alIsExtensionPresent("AL_EXT_MCFORMATS") == AL_TRUE; + + alGetError(); + + m_converter = new AUD_ConverterFactory(specs); AUD_NEW("factory") + + m_specs = specs; + m_buffersize = buffersize; + m_playing = false; + + m_playingSounds = new std::list(); AUD_NEW("list") + m_pausedSounds = new std::list(); AUD_NEW("list") + m_bufferedFactories = new std::list(); + AUD_NEW("list") + + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); + + pthread_mutex_init(&m_mutex, &attr); + + pthread_mutexattr_destroy(&attr); +} + +AUD_OpenALDevice::~AUD_OpenALDevice() +{ + AUD_OpenALHandle* sound; + + lock(); + alcSuspendContext(m_context); + + // delete all playing sounds + while(!m_playingSounds->empty()) + { + sound = *(m_playingSounds->begin()); + alDeleteSources(1, &sound->source); + if(!sound->isBuffered) + { + delete sound->reader; AUD_DELETE("reader") + alDeleteBuffers(AUD_OPENAL_CYCLE_BUFFERS, sound->buffers); + } + delete sound; AUD_DELETE("handle") + m_playingSounds->erase(m_playingSounds->begin()); + } + + // delete all paused sounds + while(!m_pausedSounds->empty()) + { + sound = *(m_pausedSounds->begin()); + alDeleteSources(1, &sound->source); + if(!sound->isBuffered) + { + delete sound->reader; AUD_DELETE("reader") + alDeleteBuffers(AUD_OPENAL_CYCLE_BUFFERS, sound->buffers); + } + delete sound; AUD_DELETE("handle") + m_pausedSounds->erase(m_pausedSounds->begin()); + } + + // delete all buffered factories + while(!m_bufferedFactories->empty()) + { + alDeleteBuffers(1, &(*(m_bufferedFactories->begin()))->buffer); + delete *m_bufferedFactories->begin(); AUD_DELETE("bufferedfactory"); + m_bufferedFactories->erase(m_bufferedFactories->begin()); + } + + alcProcessContext(m_context); + + // wait for the thread to stop + if(m_playing) + { + unlock(); + pthread_join(m_thread, NULL); + } + else + unlock(); + + delete m_playingSounds; AUD_DELETE("list") + delete m_pausedSounds; AUD_DELETE("list") + delete m_bufferedFactories; AUD_DELETE("list") + + // quit OpenAL + alcMakeContextCurrent(NULL); + alcDestroyContext(m_context); + alcCloseDevice(m_device); + + delete m_converter; AUD_DELETE("factory") + + pthread_mutex_destroy(&m_mutex); +} + +AUD_Specs AUD_OpenALDevice::getSpecs() +{ + return m_specs; +} + +bool AUD_OpenALDevice::getFormat(ALenum &format, AUD_Specs specs) +{ + bool valid = true; + format = 0; + + switch(specs.format) + { + case AUD_FORMAT_U8: + switch(specs.channels) + { + case AUD_CHANNELS_MONO: + format = AL_FORMAT_MONO8; + break; + case AUD_CHANNELS_STEREO: + format = AL_FORMAT_STEREO8; + break; + case AUD_CHANNELS_SURROUND4: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_QUAD8"); + break; + } + case AUD_CHANNELS_SURROUND51: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_51CHN8"); + break; + } + case AUD_CHANNELS_SURROUND61: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_61CHN8"); + break; + } + case AUD_CHANNELS_SURROUND71: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_71CHN8"); + break; + } + default: + valid = false; + } + break; + case AUD_FORMAT_S16: + switch(specs.channels) + { + case AUD_CHANNELS_MONO: + format = AL_FORMAT_MONO16; + break; + case AUD_CHANNELS_STEREO: + format = AL_FORMAT_STEREO16; + break; + case AUD_CHANNELS_SURROUND4: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_QUAD16"); + break; + } + case AUD_CHANNELS_SURROUND51: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_51CHN16"); + break; + } + case AUD_CHANNELS_SURROUND61: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_61CHN16"); + break; + } + case AUD_CHANNELS_SURROUND71: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_71CHN16"); + break; + } + default: + valid = false; + } + break; + case AUD_FORMAT_FLOAT32: + switch(specs.channels) + { + case AUD_CHANNELS_MONO: + format = alGetEnumValue("AL_FORMAT_MONO_FLOAT32"); + break; + case AUD_CHANNELS_STEREO: + format = alGetEnumValue("AL_FORMAT_STEREO_FLOAT32"); + break; + case AUD_CHANNELS_SURROUND4: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_QUAD32"); + break; + } + case AUD_CHANNELS_SURROUND51: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_51CHN32"); + break; + } + case AUD_CHANNELS_SURROUND61: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_61CHN32"); + break; + } + case AUD_CHANNELS_SURROUND71: + if(m_useMC) + { + format = alGetEnumValue("AL_FORMAT_71CHN32"); + break; + } + default: + valid = false; + } + break; + default: + valid = false; + } + + if(!format) + valid = false; + + return valid; +} + +AUD_Handle* AUD_OpenALDevice::play(AUD_IFactory* factory, bool keep) +{ + // check if it is a buffered factory + for(AUD_BFIterator i = m_bufferedFactories->begin(); + i != m_bufferedFactories->end(); i++) + { + if((*i)->factory == factory) + { + // create the handle + AUD_OpenALHandle* sound = new AUD_OpenALHandle; AUD_NEW("handle") + sound->keep = keep; + sound->current = -1; + sound->isBuffered = true; + sound->data_end = true; + + alcSuspendContext(m_context); + + // OpenAL playback code + try + { + alGenSources(1, &sound->source); + if(alGetError() != AL_NO_ERROR) + AUD_THROW(AUD_ERROR_OPENAL); + + try + { + alSourcei(sound->source, AL_BUFFER, (*i)->buffer); + if(alGetError() != AL_NO_ERROR) + AUD_THROW(AUD_ERROR_OPENAL); + } + catch(AUD_Exception e) + { + alDeleteSources(1, &sound->source); + throw; + } + } + catch(AUD_Exception e) + { + delete sound; AUD_DELETE("handle") + alcProcessContext(m_context); + unlock(); + throw; + } + + // play sound + m_playingSounds->push_back(sound); + + alSourcei(sound->source, AL_SOURCE_RELATIVE, 1); + start(); + + alcProcessContext(m_context); + unlock(); + + return sound; + } + } + + AUD_IReader* reader = factory->createReader(); + + if(reader == NULL) + AUD_THROW(AUD_ERROR_READER); + + AUD_Specs specs; + + specs = reader->getSpecs(); + + // check format + bool valid = true; + + if(specs.format == AUD_FORMAT_INVALID) + valid = false; + else if(specs.format == AUD_FORMAT_S24 || + specs.format == AUD_FORMAT_S32 || + specs.format == AUD_FORMAT_FLOAT32 || + specs.format == AUD_FORMAT_FLOAT64) + { + m_converter->setReader(reader); + reader = m_converter->createReader(); + specs = reader->getSpecs(); + } + + // create the handle + AUD_OpenALHandle* sound = new AUD_OpenALHandle; AUD_NEW("handle") + sound->keep = keep; + sound->reader = reader; + sound->current = 0; + sound->isBuffered = false; + sound->data_end = false; + + valid &= getFormat(sound->format, specs); + + if(!valid) + { + delete sound; AUD_DELETE("handle") + delete reader; AUD_DELETE("reader") + return NULL; + } + + lock(); + alcSuspendContext(m_context); + + // OpenAL playback code + try + { + alGenBuffers(AUD_OPENAL_CYCLE_BUFFERS, sound->buffers); + if(alGetError() != AL_NO_ERROR) + AUD_THROW(AUD_ERROR_OPENAL); + + try + { + sample_t* buf; + int length; + + for(int i = 0; i < AUD_OPENAL_CYCLE_BUFFERS; i++) + { + length = m_buffersize; + reader->read(length, buf); + alBufferData(sound->buffers[i], sound->format, buf, + length * AUD_SAMPLE_SIZE(specs), specs.rate); + if(alGetError() != AL_NO_ERROR) + AUD_THROW(AUD_ERROR_OPENAL); + } + + alGenSources(1, &sound->source); + if(alGetError() != AL_NO_ERROR) + AUD_THROW(AUD_ERROR_OPENAL); + + try + { + alSourceQueueBuffers(sound->source, AUD_OPENAL_CYCLE_BUFFERS, + sound->buffers); + if(alGetError() != AL_NO_ERROR) + AUD_THROW(AUD_ERROR_OPENAL); + } + catch(AUD_Exception e) + { + alDeleteSources(1, &sound->source); + throw; + } + } + catch(AUD_Exception e) + { + alDeleteBuffers(AUD_OPENAL_CYCLE_BUFFERS, sound->buffers); + throw; + } + } + catch(AUD_Exception e) + { + delete sound; AUD_DELETE("handle") + delete reader; AUD_DELETE("reader") + alcProcessContext(m_context); + unlock(); + throw; + } + + // play sound + m_playingSounds->push_back(sound); + alSourcei(sound->source, AL_SOURCE_RELATIVE, 1); + + start(); + + alcProcessContext(m_context); + unlock(); + + return sound; +} + +bool AUD_OpenALDevice::pause(AUD_Handle* handle) +{ + // only songs that are played can be paused + lock(); + for(AUD_HandleIterator i = m_playingSounds->begin(); + i != m_playingSounds->end(); i++) + { + if(*i == handle) + { + m_pausedSounds->push_back(*i); + alSourcePause((*i)->source); + m_playingSounds->erase(i); + unlock(); + return true; + } + } + unlock(); + return false; +} + +bool AUD_OpenALDevice::resume(AUD_Handle* handle) +{ + lock(); + + // only songs that are paused can be resumed + for(AUD_HandleIterator i = m_pausedSounds->begin(); + i != m_pausedSounds->end(); i++) + { + if(*i == handle) + { + m_playingSounds->push_back(*i); + start(); + m_pausedSounds->erase(i); + unlock(); + return true; + } + } + unlock(); + return false; +} + +bool AUD_OpenALDevice::stop(AUD_Handle* handle) +{ + AUD_OpenALHandle* sound; + + lock(); + for(AUD_HandleIterator i = m_playingSounds->begin(); + i != m_playingSounds->end(); i++) + { + if(*i == handle) + { + sound = *i; + alDeleteSources(1, &sound->source); + if(!sound->isBuffered) + { + delete sound->reader; AUD_DELETE("reader") + alDeleteBuffers(AUD_OPENAL_CYCLE_BUFFERS, sound->buffers); + } + delete *i; AUD_DELETE("handle") + m_playingSounds->erase(i); + unlock(); + return true; + } + } + for(AUD_HandleIterator i = m_pausedSounds->begin(); + i != m_pausedSounds->end(); i++) + { + if(*i == handle) + { + sound = *i; + alDeleteSources(1, &sound->source); + if(!sound->isBuffered) + { + delete sound->reader; AUD_DELETE("reader") + alDeleteBuffers(AUD_OPENAL_CYCLE_BUFFERS, sound->buffers); + } + delete *i; AUD_DELETE("handle") + m_pausedSounds->erase(i); + unlock(); + return true; + } + } + unlock(); + return false; +} + +bool AUD_OpenALDevice::setKeep(AUD_Handle* handle, bool keep) +{ + lock(); + if(isValid(handle)) + { + ((AUD_OpenALHandle*)handle)->keep = keep; + unlock(); + return true; + } + unlock(); + return false; +} + +bool AUD_OpenALDevice::sendMessage(AUD_Handle* handle, AUD_Message &message) +{ + lock(); + + bool result = false; + + if(handle == 0) + { + for(AUD_HandleIterator i = m_playingSounds->begin(); + i != m_playingSounds->end(); i++) + if(!(*i)->isBuffered) + result |= (*i)->reader->notify(message); + for(AUD_HandleIterator i = m_pausedSounds->begin(); + i != m_pausedSounds->end(); i++) + if(!(*i)->isBuffered) + result |= (*i)->reader->notify(message); + } + else if(isValid(handle)) + if(!((AUD_OpenALHandle*)handle)->isBuffered) + result = ((AUD_OpenALHandle*)handle)->reader->notify(message); + unlock(); + return result; +} + +bool AUD_OpenALDevice::seek(AUD_Handle* handle, float position) +{ + lock(); + + if(isValid(handle)) + { + AUD_OpenALHandle* alhandle = (AUD_OpenALHandle*)handle; + if(alhandle->isBuffered) + alSourcef(alhandle->source, AL_SEC_OFFSET, position); + else + { + alhandle->reader->seek((int)(position * + alhandle->reader->getSpecs().rate)); + alhandle->data_end = false; + + ALint info; + + alGetSourcei(alhandle->source, AL_SOURCE_STATE, &info); + + if(info != AL_PLAYING) + { + if(info != AL_STOPPED) + alSourceStop(alhandle->source); + + alSourceUnqueueBuffers(alhandle->source, + AUD_OPENAL_CYCLE_BUFFERS, + alhandle->buffers); + if(alGetError() == AL_NO_ERROR) + { + sample_t* buf; + int length; + AUD_Specs specs = alhandle->reader->getSpecs(); + + for(int i = 0; i < AUD_OPENAL_CYCLE_BUFFERS; i++) + { + length = m_buffersize; + alhandle->reader->read(length, buf); + alBufferData(alhandle->buffers[i], alhandle->format, + buf, length * AUD_SAMPLE_SIZE(specs), + specs.rate); + + if(alGetError() != AL_NO_ERROR) + break; + } + + alSourceQueueBuffers(alhandle->source, + AUD_OPENAL_CYCLE_BUFFERS, + alhandle->buffers); + } + + alSourceRewind(alhandle->source); + } + } + unlock(); + return true; + } + + unlock(); + return false; +} + +float AUD_OpenALDevice::getPosition(AUD_Handle* handle) +{ + lock(); + + float position = 0.0; + + if(isValid(handle)) + { + AUD_OpenALHandle* h = (AUD_OpenALHandle*)handle; + if(h->isBuffered) + alGetSourcef(h->source, AL_SEC_OFFSET, &position); + else + position = h->reader->getPosition() / + (float)h->reader->getSpecs().rate; + } + + unlock(); + return position; +} + +AUD_Status AUD_OpenALDevice::getStatus(AUD_Handle* handle) +{ + lock(); + for(AUD_HandleIterator i = m_playingSounds->begin(); + i != m_playingSounds->end(); i++) + { + if(*i == handle) + { + unlock(); + return AUD_STATUS_PLAYING; + } + } + for(AUD_HandleIterator i = m_pausedSounds->begin(); + i != m_pausedSounds->end(); i++) + { + if(*i == handle) + { + unlock(); + return AUD_STATUS_PAUSED; + } + } + unlock(); + return AUD_STATUS_INVALID; +} + +void AUD_OpenALDevice::lock() +{ + pthread_mutex_lock(&m_mutex); +} + +void AUD_OpenALDevice::unlock() +{ + pthread_mutex_unlock(&m_mutex); +} + +/******************************************************************************/ +/**************************** Capabilities Code *******************************/ +/******************************************************************************/ + +bool AUD_OpenALDevice::checkCapability(int capability) +{ + return capability == AUD_CAPS_3D_DEVICE || + capability == AUD_CAPS_VOLUME || + capability == AUD_CAPS_SOURCE_VOLUME || + capability == AUD_CAPS_SOURCE_PITCH || + capability == AUD_CAPS_BUFFERED_FACTORY; +} + +bool AUD_OpenALDevice::setCapability(int capability, void *value) +{ + switch(capability) + { + case AUD_CAPS_VOLUME: + alListenerf(AL_GAIN, *((float*)value)); + return true; + case AUD_CAPS_SOURCE_VOLUME: + { + AUD_SourceCaps* caps = (AUD_SourceCaps*) value; + lock(); + if(isValid(caps->handle)) + { + alSourcef(((AUD_OpenALHandle*)caps->handle)->source, + AL_GAIN, caps->value); + unlock(); + return true; + } + unlock(); + } + break; + case AUD_CAPS_SOURCE_PITCH: + { + AUD_SourceCaps* caps = (AUD_SourceCaps*) value; + lock(); + if(isValid(caps->handle)) + { + alSourcef(((AUD_OpenALHandle*)caps->handle)->source, + AL_PITCH, caps->value); + unlock(); + return true; + } + unlock(); + } + break; + case AUD_CAPS_BUFFERED_FACTORY: + { + AUD_IFactory* factory = (AUD_IFactory*) value; + + // load the factory into an OpenAL buffer + if(factory) + { + lock(); + for(AUD_BFIterator i = m_bufferedFactories->begin(); + i != m_bufferedFactories->end(); i++) + { + if((*i)->factory == factory) + { + unlock(); + return true; + } + } + unlock(); + + AUD_IReader* reader = factory->createReader(); + + if(reader == NULL) + return false; + + AUD_Specs specs; + + specs = reader->getSpecs(); + + // determine format + bool valid = reader->getType() == AUD_TYPE_BUFFER; + + if(valid) + { + if(specs.format == AUD_FORMAT_INVALID) + valid = false; + else if(specs.format == AUD_FORMAT_S24 || + specs.format == AUD_FORMAT_S32 || + specs.format == AUD_FORMAT_FLOAT32 || + specs.format == AUD_FORMAT_FLOAT64) + { + m_converter->setReader(reader); + reader = m_converter->createReader(); + specs = reader->getSpecs(); + } + } + + ALenum format; + + if(valid) + valid = getFormat(format, specs); + + if(!valid) + { + delete reader; AUD_DELETE("reader") + return false; + } + + // load into a buffer + lock(); + alcSuspendContext(m_context); + + AUD_OpenALBufferedFactory* bf = new AUD_OpenALBufferedFactory; + AUD_NEW("bufferedfactory"); + bf->factory = factory; + + try + { + alGenBuffers(1, &bf->buffer); + if(alGetError() != AL_NO_ERROR) + AUD_THROW(AUD_ERROR_OPENAL); + + try + { + sample_t* buf; + int length = reader->getLength(); + + reader->read(length, buf); + alBufferData(bf->buffer, format, buf, + length * AUD_SAMPLE_SIZE(specs), + specs.rate); + if(alGetError() != AL_NO_ERROR) + AUD_THROW(AUD_ERROR_OPENAL); + } + catch(AUD_Exception e) + { + alDeleteBuffers(1, &bf->buffer); + throw; + } + } + catch(AUD_Exception e) + { + delete bf; AUD_DELETE("bufferedfactory") + delete reader; AUD_DELETE("reader") + alcProcessContext(m_context); + unlock(); + return false; + } + + m_bufferedFactories->push_back(bf); + + alcProcessContext(m_context); + unlock(); + } + else + { + // stop all playing and paused buffered sources + lock(); + alcSuspendContext(m_context); + + AUD_OpenALHandle* sound; + AUD_HandleIterator it = m_playingSounds->begin(); + while(it != m_playingSounds->end()) + { + sound = *it; + ++it; + + if(sound->isBuffered) + stop(sound); + } + alcProcessContext(m_context); + + while(!m_bufferedFactories->empty()) + { + alDeleteBuffers(1, + &(*(m_bufferedFactories->begin()))->buffer); + delete *m_bufferedFactories->begin(); + AUD_DELETE("bufferedfactory"); + m_bufferedFactories->erase(m_bufferedFactories->begin()); + } + unlock(); + } + + return true; + } + break; + } + return false; +} + +bool AUD_OpenALDevice::getCapability(int capability, void *value) +{ + switch(capability) + { + case AUD_CAPS_VOLUME: + alGetListenerf(AL_GAIN, (float*)value); + return true; + case AUD_CAPS_SOURCE_VOLUME: + { + AUD_SourceCaps* caps = (AUD_SourceCaps*) value; + lock(); + if(isValid(caps->handle)) + { + alGetSourcef(((AUD_OpenALHandle*)caps->handle)->source, + AL_GAIN, &caps->value); + unlock(); + return true; + } + unlock(); + } + break; + case AUD_CAPS_SOURCE_PITCH: + { + AUD_SourceCaps* caps = (AUD_SourceCaps*) value; + lock(); + if(isValid(caps->handle)) + { + alGetSourcef(((AUD_OpenALHandle*)caps->handle)->source, + AL_PITCH, &caps->value); + unlock(); + return true; + } + unlock(); + } + break; + } + return false; +} + +/******************************************************************************/ +/**************************** 3D Device Code **********************************/ +/******************************************************************************/ + +AUD_Handle* AUD_OpenALDevice::play3D(AUD_IFactory* factory, bool keep) +{ + AUD_OpenALHandle* handle = (AUD_OpenALHandle*)play(factory, keep); + if(handle) + alSourcei(handle->source, AL_SOURCE_RELATIVE, 0); + return handle; +} + +bool AUD_OpenALDevice::updateListener(AUD_3DData &data) +{ + alListenerfv(AL_POSITION, (ALfloat*)data.position); + alListenerfv(AL_VELOCITY, (ALfloat*)data.velocity); + alListenerfv(AL_ORIENTATION, (ALfloat*)&(data.orientation[3])); + + return true; +} + +bool AUD_OpenALDevice::setSetting(AUD_3DSetting setting, float value) +{ + switch(setting) + { + case AUD_3DS_DISTANCE_MODEL: + if(value == AUD_DISTANCE_MODEL_NONE) + alDistanceModel(AL_NONE); + else if(value == AUD_DISTANCE_MODEL_INVERSE) + alDistanceModel(AL_INVERSE_DISTANCE); + else if(value == AUD_DISTANCE_MODEL_INVERSE_CLAMPED) + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); + else if(value == AUD_DISTANCE_MODEL_LINEAR) + alDistanceModel(AL_LINEAR_DISTANCE); + else if(value == AUD_DISTANCE_MODEL_LINEAR_CLAMPED) + alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); + else if(value == AUD_DISTANCE_MODEL_EXPONENT) + alDistanceModel(AL_EXPONENT_DISTANCE); + else if(value == AUD_DISTANCE_MODEL_EXPONENT_CLAMPED) + alDistanceModel(AL_EXPONENT_DISTANCE_CLAMPED); + else + return false; + return true; + case AUD_3DS_DOPPLER_FACTOR: + alDopplerFactor(value); + return true; + case AUD_3DS_SPEED_OF_SOUND: + alSpeedOfSound(value); + return true; + default: + return false; + } +} + +float AUD_OpenALDevice::getSetting(AUD_3DSetting setting) +{ + switch(setting) + { + case AUD_3DS_DISTANCE_MODEL: + switch(alGetInteger(AL_DISTANCE_MODEL)) + { + case AL_NONE: + return AUD_DISTANCE_MODEL_NONE; + case AL_INVERSE_DISTANCE: + return AUD_DISTANCE_MODEL_INVERSE; + case AL_INVERSE_DISTANCE_CLAMPED: + return AUD_DISTANCE_MODEL_INVERSE_CLAMPED; + case AL_LINEAR_DISTANCE: + return AUD_DISTANCE_MODEL_LINEAR; + case AL_LINEAR_DISTANCE_CLAMPED: + return AUD_DISTANCE_MODEL_LINEAR_CLAMPED; + case AL_EXPONENT_DISTANCE: + return AUD_DISTANCE_MODEL_EXPONENT; + case AL_EXPONENT_DISTANCE_CLAMPED: + return AUD_DISTANCE_MODEL_EXPONENT_CLAMPED; + } + case AUD_3DS_DOPPLER_FACTOR: + return alGetFloat(AL_DOPPLER_FACTOR); + case AUD_3DS_SPEED_OF_SOUND: + return alGetFloat(AL_SPEED_OF_SOUND); + default: + return std::numeric_limits::quiet_NaN(); + } +} + +bool AUD_OpenALDevice::updateSource(AUD_Handle* handle, AUD_3DData &data) +{ + lock(); + + if(isValid(handle)) + { + int source = ((AUD_OpenALHandle*)handle)->source; + alSourcefv(source, AL_POSITION, (ALfloat*)data.position); + alSourcefv(source, AL_VELOCITY, (ALfloat*)data.velocity); + alSourcefv(source, AL_DIRECTION, (ALfloat*)&(data.orientation[3])); + unlock(); + return true; + } + + unlock(); + return false; +} + +bool AUD_OpenALDevice::setSourceSetting(AUD_Handle* handle, + AUD_3DSourceSetting setting, + float value) +{ + lock(); + + bool result = false; + + if(isValid(handle)) + { + int source = ((AUD_OpenALHandle*)handle)->source; + + switch(setting) + { + case AUD_3DSS_CONE_INNER_ANGLE: + alSourcef(source, AL_CONE_INNER_ANGLE, value); + result = true; + break; + case AUD_3DSS_CONE_OUTER_ANGLE: + alSourcef(source, AL_CONE_OUTER_ANGLE, value); + result = true; + break; + case AUD_3DSS_CONE_OUTER_GAIN: + alSourcef(source, AL_CONE_OUTER_GAIN, value); + result = true; + break; + case AUD_3DSS_IS_RELATIVE: + alSourcei(source, AL_SOURCE_RELATIVE, value > 0.0); + result = true; + break; + case AUD_3DSS_MAX_DISTANCE: + alSourcef(source, AL_MAX_DISTANCE, value); + result = true; + break; + case AUD_3DSS_MAX_GAIN: + alSourcef(source, AL_MAX_GAIN, value); + result = true; + break; + case AUD_3DSS_MIN_GAIN: + alSourcef(source, AL_MIN_GAIN, value); + result = true; + break; + case AUD_3DSS_REFERENCE_DISTANCE: + alSourcef(source, AL_REFERENCE_DISTANCE, value); + result = true; + break; + case AUD_3DSS_ROLLOFF_FACTOR: + alSourcef(source, AL_ROLLOFF_FACTOR, value); + result = true; + break; + default: + break; + } + } + + unlock(); + return result; +} + +float AUD_OpenALDevice::getSourceSetting(AUD_Handle* handle, + AUD_3DSourceSetting setting) +{ + float result = std::numeric_limits::quiet_NaN();; + + lock(); + + if(isValid(handle)) + { + int source = ((AUD_OpenALHandle*)handle)->source; + + switch(setting) + { + case AUD_3DSS_CONE_INNER_ANGLE: + alGetSourcef(source, AL_CONE_INNER_ANGLE, &result); + break; + case AUD_3DSS_CONE_OUTER_ANGLE: + alGetSourcef(source, AL_CONE_OUTER_ANGLE, &result); + break; + case AUD_3DSS_CONE_OUTER_GAIN: + alGetSourcef(source, AL_CONE_OUTER_GAIN, &result); + break; + case AUD_3DSS_IS_RELATIVE: + { + ALint i; + alGetSourcei(source, AL_SOURCE_RELATIVE, &i); + result = i ? 1.0 : 0.0; + break; + } + case AUD_3DSS_MAX_DISTANCE: + alGetSourcef(source, AL_MAX_DISTANCE, &result); + break; + case AUD_3DSS_MAX_GAIN: + alGetSourcef(source, AL_MAX_GAIN, &result); + break; + case AUD_3DSS_MIN_GAIN: + alGetSourcef(source, AL_MIN_GAIN, &result); + break; + case AUD_3DSS_REFERENCE_DISTANCE: + alGetSourcef(source, AL_REFERENCE_DISTANCE, &result); + break; + case AUD_3DSS_ROLLOFF_FACTOR: + alGetSourcef(source, AL_ROLLOFF_FACTOR, &result); + break; + default: + break; + } + } + + unlock(); + return result; +} diff --git a/intern/audaspace/OpenAL/AUD_OpenALDevice.h b/intern/audaspace/OpenAL/AUD_OpenALDevice.h new file mode 100644 index 00000000000..074cd3d1924 --- /dev/null +++ b/intern/audaspace/OpenAL/AUD_OpenALDevice.h @@ -0,0 +1,171 @@ +/* + * $Id$ + * + * ***** BEGIN LGPL LICENSE BLOCK ***** + * + * Copyright 2009 Jörg Hermann Müller + * + * This file is part of AudaSpace. + * + * AudaSpace is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * AudaSpace is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with AudaSpace. If not, see . + * + * ***** END LGPL LICENSE BLOCK ***** + */ + +#ifndef AUD_OPENALDEVICE +#define AUD_OPENALDEVICE + +#include "AUD_IDevice.h" +#include "AUD_I3DDevice.h" +struct AUD_OpenALHandle; +struct AUD_OpenALBufferedFactory; +class AUD_ConverterFactory; + +#include +#include +#include +#include + +/** + * This device plays through OpenAL. + */ +class AUD_OpenALDevice : public AUD_IDevice, public AUD_I3DDevice +{ +private: + /** + * The OpenAL device handle. + */ + ALCdevice* m_device; + + /** + * The OpenAL context. + */ + ALCcontext* m_context; + + /** + * The specification of the device. + */ + AUD_Specs m_specs; + + /** + * Whether the device has the AL_EXT_MCFORMATS extension. + */ + bool m_useMC; + + /** + * The converter factory for readers with wrong input format. + */ + AUD_ConverterFactory* m_converter; + + /** + * The list of sounds that are currently playing. + */ + std::list* m_playingSounds; + + /** + * The list of sounds that are currently paused. + */ + std::list* m_pausedSounds; + + /** + * The list of buffered factories. + */ + std::list* m_bufferedFactories; + + /** + * The mutex for locking. + */ + pthread_mutex_t m_mutex; + + /** + * The streaming thread. + */ + pthread_t m_thread; + + /** + * The condition for streaming thread wakeup. + */ + bool m_playing; + + /** + * Buffer size. + */ + int m_buffersize; + + /** + * Starts the streaming thread. + */ + void start(); + + /** + * Checks if a handle is valid. + * \param handle The handle to check. + * \return Whether the handle is valid. + */ + bool isValid(AUD_Handle* handle); + + /** + * Gets the format according to the specs. + * \param format The variable to put the format into. + * \param specs The specs to read the format from. + * \return Whether the format is valid or not. + */ + bool getFormat(ALenum &format, AUD_Specs specs); + +public: + /** + * Opens the OpenAL audio device for playback. + * \param specs The wanted audio specification. + * \param buffersize The size of the internal buffer. + * \note The specification really used for opening the device may differ. + * \note The buffersize will be multiplicated by three for this device. + * \exception AUD_Exception Thrown if the audio device cannot be opened. + */ + AUD_OpenALDevice(AUD_Specs specs, int buffersize = AUD_DEFAULT_BUFFER_SIZE); + + /** + * Streaming thread main function. + */ + void updateStreams(); + + virtual ~AUD_OpenALDevice(); + + virtual AUD_Specs getSpecs(); + virtual AUD_Handle* play(AUD_IFactory* factory, bool keep = false); + virtual bool pause(AUD_Handle* handle); + virtual bool resume(AUD_Handle* handle); + virtual bool stop(AUD_Handle* handle); + virtual bool setKeep(AUD_Handle* handle, bool keep); + virtual bool sendMessage(AUD_Handle* handle, AUD_Message &message); + virtual bool seek(AUD_Handle* handle, float position); + virtual float getPosition(AUD_Handle* handle); + virtual AUD_Status getStatus(AUD_Handle* handle); + virtual void lock(); + virtual void unlock(); + virtual bool checkCapability(int capability); + virtual bool setCapability(int capability, void *value); + virtual bool getCapability(int capability, void *value); + + virtual AUD_Handle* play3D(AUD_IFactory* factory, bool keep = false); + virtual bool updateListener(AUD_3DData &data); + virtual bool setSetting(AUD_3DSetting setting, float value); + virtual float getSetting(AUD_3DSetting setting); + virtual bool updateSource(AUD_Handle* handle, AUD_3DData &data); + virtual bool setSourceSetting(AUD_Handle* handle, + AUD_3DSourceSetting setting, float value); + virtual float getSourceSetting(AUD_Handle* handle, + AUD_3DSourceSetting setting); +}; + +#endif //AUD_OPENALDEVICE -- cgit v1.2.3