summaryrefslogtreecommitdiffstats
path: root/src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp')
-rw-r--r--src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp523
1 files changed, 523 insertions, 0 deletions
diff --git a/src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp b/src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp
new file mode 100644
index 0000000..4266f79
--- /dev/null
+++ b/src/uscxml/plugins/invoker/audio/OpenALPlayer.cpp
@@ -0,0 +1,523 @@
+#include "OpenALPlayer.h"
+#include <assert.h>
+#include <stdexcept>
+
+tthread::recursive_mutex OpenALPlayer::_alMutex;
+
+/**
+* Create a new OpenAL stream source
+*/
+OpenALPlayer::OpenALPlayer(ALCcontext* context, OpenALPlayerCallback* audioCallback, ALenum format, ALsizei freq) {
+
+ _isInitialized = false;
+ _audioCallback = audioCallback;
+ _freq = freq;
+ _format = format;
+ _bufferSize = 0;
+ _nrBuffers = 0;
+ _thread = NULL;
+ _alId = 0;
+
+ _position[0] = _position[1] = _position[2] = 0;
+ _velocity[0] = _velocity[1] = _velocity[2] = 0;
+ _direction[0] = _direction[1] = _direction[2] = 0;
+
+ tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex);
+ if (context == NULL) {
+ // this is in essence alutInit() from freealut.
+
+ // use the current context if there is one
+ _context = alcGetCurrentContext();
+
+ if (_context == NULL) {
+// std::cout << "\tnew context" << std::endl;
+ // create a new context if none was given and no is current
+
+ // get default device
+ ALCdevice* device = alcOpenDevice(NULL);
+ if (device == NULL) {
+ throw std::runtime_error("__FILE__ __LINE__ openal error opening device");
+ }
+
+ // create new context with device
+ _context = alcCreateContext (device, NULL);
+ if (_context == NULL) {
+ alcCloseDevice (device);
+ throw std::runtime_error("openal error create context");
+ }
+
+ // make context current
+ if (!alcMakeContextCurrent (_context)) {
+ alcDestroyContext (_context);
+ alcCloseDevice (device);
+ throw std::runtime_error("openal error make context current");
+ }
+ } else {
+// std::cout << "\texisting context" << std::endl;
+ }
+ } else {
+// std::cout << "\tgiven context" << std::endl;
+ _context = context;
+ }
+}
+
+OpenALPlayer::~OpenALPlayer() {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex);
+ if (isPlaying()) {
+ alSourceStop(_alId);
+ }
+ if (_thread) {
+ stop();
+ _thread->join();
+ delete(_thread);
+ }
+
+ if (_isInitialized) {
+ alDeleteSources(1, &_alId);
+ if (alIsSource(_alId)) {
+ throw std::runtime_error("openal source id still valid");
+ }
+ for (int i = 0; i < _nrBuffers; i++) {
+ assert(alIsBuffer(_bufferIds[i]));
+ free(_buffers[i]);
+ }
+ alDeleteBuffers(_nrBuffers, _bufferIds);
+ for (int i = 0; i < _nrBuffers; i++) {
+ assert(!alIsBuffer(_bufferIds[i]));
+ }
+ free(_buffers);
+ free(_bufferIds);
+ }
+ // clear errors and begone
+ alGetError();
+
+}
+
+/**
+* Allocate; data and set defaults
+*/
+void OpenALPlayer::init() {
+ _userData = NULL;
+ _pitch = 0;
+ _gain = 0;
+ _referenceDistance = 1.0;
+ _isLooping = false;
+
+ // no one set a buffer size yet
+ if (_bufferSize <= 0)
+ _bufferSize = ALPLAY_AUDIO_BUFFER_SIZE;
+
+ // no one set the number of buffers yet
+ if (_nrBuffers <= 0)
+ _nrBuffers = ALPLAY_NR_AUDIO_BUFFERS;
+
+ _isInitialized = true;
+
+ _buffers = (char**)malloc(_nrBuffers * sizeof(char*));
+ _bufferIds = (ALuint*)malloc(_nrBuffers * sizeof(ALuint));
+ for (int i = 0; i < _nrBuffers; i++) {
+ _buffers[i] = 0; //(char*)malloc(_bufferSize);
+ }
+
+ // there are other formats as well and this will have to be extended
+ int bytesPerSample = 2;
+ switch(_format) {
+ case AL_FORMAT_MONO8:
+ case AL_FORMAT_STEREO8:
+ bytesPerSample = 1;
+ break;
+
+ case AL_FORMAT_MONO16:
+ case AL_FORMAT_STEREO16:
+ bytesPerSample = 2;
+ break;
+ }
+
+ // how many ms audio is in one buffer?
+ _msForBuffer = (int)(((float)_bufferSize / (float)bytesPerSample) / ((float)_freq / 1000.0f));
+ _initialSleep = (_msForBuffer - (int)(0.6 * _msForBuffer)) * _nrBuffers;
+ _bufferSleep = _msForBuffer - (int)(0.4 * _msForBuffer);
+ _repollSleep = _msForBuffer - (int)(0.7 * _msForBuffer);
+
+// std::cout << _msForBuffer << "ms in one buffer" << std::endl;
+
+ // get available buffer ids
+ tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex);
+
+ if (!alcMakeContextCurrent (_context)) {
+ throw std::runtime_error("openal error make context current");
+ }
+
+ alGenBuffers(_nrBuffers, _bufferIds);
+ checkOpenALError(__LINE__);
+
+ // get new source id from openAL
+ alGenSources(1, &_alId);
+
+ checkOpenALError(__LINE__);
+ if (!alIsSource(_alId)) {
+ throw std::runtime_error("openal source id not valid");
+ }
+
+ // set our position and various flags to meaningful defaults
+ alSourcei(_alId, AL_LOOPING, AL_FALSE);
+ checkOpenALError(__LINE__);
+ alSourcefv(_alId, AL_POSITION, _position);
+ checkOpenALError(__LINE__);
+ alSourcef(_alId,AL_REFERENCE_DISTANCE, 5.0f);
+ checkOpenALError(__LINE__);
+// alDistanceModel(AL_LINEAR_DISTANCE);
+// checkOpenALError(__LINE__);
+// alSourcefv(_alId, AL_VELOCITY, _velocity);
+// checkOpenALError(__LINE__);
+// alSourcefv(_alId, AL_DIRECTION, _direction);
+// checkOpenALError(__LINE__);
+// alSourcef (_alId, AL_ROLLOFF_FACTOR, 1.0);
+// checkOpenALError(__LINE__);
+ alSourcef(_alId,AL_REFERENCE_DISTANCE, 5.0f);
+ checkOpenALError(__LINE__);
+// float listener[] = { 0.0, 0.0, 0.0 };
+// alListenerfv(AL_POSITION, listener);
+// checkOpenALError(__LINE__);
+
+ alSourcei (_alId, AL_SOURCE_RELATIVE, AL_TRUE);
+ checkOpenALError(__LINE__);
+}
+
+/**
+* Start the sound source.
+*
+* This will trigger continuous calls top the audio callback.
+*/
+void OpenALPlayer::start() {
+ if (!_isInitialized)
+ init();
+
+ if (_audioCallback == NULL)
+ throw std::runtime_error("cannot start without an audio callback");
+
+ _isStarted = true;
+
+ // prime the buffers with some initial data and register for buffer ids
+ tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex);
+ for (ALuint i = 0; i < (unsigned int)_nrBuffers; i++) {
+ _buffers[i] = (char*)malloc(_bufferSize);
+ _audioCallback->getSamples(_buffers[i], _bufferSize, this);
+ alBufferData(_bufferIds[i], _format, _buffers[i], _bufferSize, _freq);
+ checkOpenALError(__LINE__);
+ }
+ // enqueue all buffers
+ alSourceQueueBuffers(_alId, _nrBuffers, _bufferIds);
+ checkOpenALError(__LINE__);
+
+ // start thread
+ if (_audioCallback != NULL) {
+ _thread = new tthread::thread(&OpenALPlayer::updateBuffersWrapper, this);
+ }
+
+ // tell openAL to start rendering the buffers
+ alSourcePlay(_alId);
+ checkOpenALError(__LINE__);
+}
+
+// find bufferId in _bufferIds to get bufferIndex into _buffers - messy
+int OpenALPlayer::bufferIndex(int bufferId) {
+ int bufferIndex = 0;
+ for (; bufferIndex < _nrBuffers; bufferIndex++) {
+ if (_bufferIds[bufferIndex] == (unsigned int)bufferId)
+ break;
+ }
+ if (bufferIndex >= _nrBuffers)
+ throw std::runtime_error("could not find dequeued bufferId in ids");
+ return bufferIndex;
+}
+
+/**
+* Write a buffer (blocking).
+*
+* This allows for a pushing model, whereas the callback allows for a polling model.
+*/
+int OpenALPlayer::write(char* buffer, int size, int* repollAt, bool blocking) {
+ tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex);
+
+ if (!_isInitialized)
+ init();
+
+ if (_audioCallback != NULL) {
+ throw std::runtime_error("you cannot use the write interface with an audio callback");
+ }
+
+ if (size != _bufferSize) {
+ throw std::runtime_error("buffersize does not match");
+ }
+
+ if (!alcMakeContextCurrent (_context)) {
+ throw std::runtime_error("openal error make context current");
+ }
+
+ // try to enqueue the given buffer data
+ for (;;) {
+ // do we have an empty buffer in the OpenAL queue?
+ int processed;
+ alGetSourcei(_alId, AL_BUFFERS_PROCESSED, &processed);
+ checkOpenALError(__LINE__);
+
+// if (!isPlaying())
+// std::cout << "-";
+
+ if (processed > 0) {
+// std::cout << "P" << processed;
+ ALuint bufferId = 0;
+ alSourceUnqueueBuffers(_alId, 1, &bufferId);
+ checkOpenALError(__LINE__);
+
+ int bufferIdx = bufferIndex(bufferId);
+
+ // fill the buffer with the given data
+ memcpy(_buffers[bufferIdx], buffer, _bufferSize);
+ alBufferData(bufferId, _format, _buffers[bufferIdx], _bufferSize, _freq);
+ checkOpenALError(__LINE__);
+
+ // enqueue
+ alSourceQueueBuffers(_alId, 1, &bufferId);
+ checkOpenALError(__LINE__);
+
+ // some buffers were processed
+ if (repollAt)
+ *repollAt = _repollSleep;
+ break;
+
+ } else {
+ // no buffer processed - is there an uninitialized buffer left?
+ int nextBuffer = 0;
+ for(; nextBuffer < _nrBuffers; nextBuffer++) {
+ if (_buffers[nextBuffer] == 0) {
+ break;
+ }
+ }
+ if (nextBuffer < _nrBuffers) {
+// std::cout << "N";
+ _buffers[nextBuffer] = (char*)malloc(_bufferSize);
+ memcpy(_buffers[nextBuffer], buffer, _bufferSize);
+
+ alBufferData(_bufferIds[nextBuffer], _format, _buffers[nextBuffer], _bufferSize, _freq);
+ checkOpenALError(__LINE__);
+
+ alSourceQueueBuffers(_alId, 1, &_bufferIds[nextBuffer]);
+ checkOpenALError(__LINE__);
+ // there was a free buffer, repoll immediately to try to write more
+ if (repollAt)
+ *repollAt = 0;
+
+ break;
+ } else {
+// std::cout << "X";
+ // no processed, no new buffer, wait until we processed one
+ if (blocking) {
+ tthread::this_thread::sleep_for(tthread::chrono::milliseconds(_repollSleep));
+ } else {
+ if (repollAt)
+ *repollAt = _repollSleep;
+ return -1;
+ }
+ }
+ }
+ }
+
+ // we have at least one buffer queued, start playing
+ if (!_isStarted || !isPlaying()) {
+ alSourcePlay(_alId);
+ checkOpenALError(__LINE__);
+ _isStarted = true;
+ }
+
+ return size;
+}
+
+
+/**
+* Dequeue, refill and re-enqueue buffers.
+*/
+void OpenALPlayer::updateBuffers() {
+ int processed;
+// int queued;
+
+// std::cout << "Initial sleep: " << initialSleep << "ms" << std::endl;
+// std::cout << "Buffer sleep: " << bufferSleep << "ms" << std::endl;
+// std::cout << "Repoll sleep: " << repollSleep << "ms" << std::endl;
+ tthread::this_thread::sleep_for(tthread::chrono::milliseconds(_bufferSleep * _initialSleep));
+
+
+ while(_isStarted) {
+
+ // how many buffers have been rendered already?
+ tthread::lock_guard<tthread::recursive_mutex> lock(_alMutex);
+ alGetSourcei(_alId, AL_BUFFERS_PROCESSED, &processed);
+ checkOpenALError(__LINE__);
+ //std::cout << processed << std::flush;
+
+ if (processed == 0) {
+ // avoid busy wait by sleeping
+ tthread::this_thread::sleep_for(tthread::chrono::milliseconds(_bufferSleep * _initialSleep));
+ } else {
+ // dequeue buffers and get ids
+ // see http://stackoverflow.com/questions/1900665/c-compiler-differences-vs2008-and-g
+ ALuint bufferIds[ALPLAY_NR_AUDIO_BUFFERS];
+ alSourceUnqueueBuffers(_alId, processed, bufferIds);
+ checkOpenALError(__LINE__);
+
+ for (int id = 0; id < processed; id++) {
+ int bufferIdx = bufferIndex(bufferIds[id]);
+
+ // refill the buffer with data from the callback
+ _audioCallback->getSamples(_buffers[bufferIdx], _bufferSize, this);
+ alBufferData(bufferIds[id], _format, _buffers[bufferIdx], _bufferSize, _freq);
+ checkOpenALError(__LINE__);
+
+ }
+ // re-enqueue
+ alSourceQueueBuffers(_alId, processed, bufferIds);
+ checkOpenALError(__LINE__);
+
+ // restart if we are not running anymore
+ if (!isPlaying()) {
+ alSourcePlay(_alId);
+ checkOpenALError(__LINE__);
+ }
+
+ // sleep a bit less than the duration of one buffer
+ tthread::this_thread::sleep_for(tthread::chrono::milliseconds(_bufferSleep * processed));
+ }
+ }
+}
+
+/**
+* TODO
+*/
+void OpenALPlayer::stop() {
+ _isStarted = false;
+ _thread->join();
+}
+
+void OpenALPlayer::checkOpenALError(int line) {
+ int error = alGetError();
+ if(error != AL_NO_ERROR) {
+ std::stringstream out;
+ out << "OpenALError:" << line << ":";
+
+ switch (error) {
+ case AL_INVALID_NAME:
+ out << "OpenAL invalid name.";
+ break;
+ case AL_INVALID_ENUM:
+ out << "OpenAL invalid enum.";
+ break;
+ case AL_INVALID_VALUE:
+ out << "OpenAL invalid value.";
+ break;
+ case AL_INVALID_OPERATION:
+ out << "OpenAL invalid operation.";
+ break;
+ case AL_OUT_OF_MEMORY:
+ out << "OpenAL out of memory.";
+ break;
+
+ default:
+ out << "OpenAL unknown error.";
+ break;
+ }
+ throw std::runtime_error(out.str());
+ }
+}
+
+unsigned int OpenALPlayer::isPlaying() {
+ ALint val;
+ alGetSourcei(_alId, AL_SOURCE_STATE, &val);
+ if(val != AL_PLAYING)
+ return 0;
+ return _repollSleep;
+}
+
+void OpenALPlayer::updateBuffersWrapper(void *obj) {
+ try {
+ reinterpret_cast<OpenALPlayer *>(obj)->updateBuffers();
+ } catch(std::runtime_error& error) {
+// std::cout << "Terminating Thread: " << error << std::endl;
+ } catch(...) {
+// std::cout << "Terminating Thread! " << std::endl;
+ }
+}
+
+void OpenALPlayer::setNrBuffers(int nrBuffers) {
+ if (_nrBuffers > 0)
+ throw std::runtime_error("cannot modify number of buffers");
+ _nrBuffers = nrBuffers;
+}
+
+int OpenALPlayer::getNrBuffers() {
+ return _nrBuffers;
+}
+
+/**
+* Set position of sound source in coordinate system
+*/
+void OpenALPlayer::setPosition(ALfloat position[]) {
+ memcpy(&_position, position, 3 * sizeof(ALfloat));
+// std::cout << _position[0] << ", " << _position[1] << ", " << _position[2] << std::endl;
+ if (_isInitialized)
+ alSourcefv(_alId, AL_POSITION, _position);
+}
+
+ALfloat* OpenALPlayer::getPosition() {
+ return _position;
+}
+
+/**
+* Set velocity of sound source in coordinate system
+*/
+void OpenALPlayer::setVelocity(ALfloat velocity[]) {
+ memcpy(&_velocity, velocity, 3 * sizeof(ALfloat));
+ if (_isInitialized)
+ alSourcefv(_alId, AL_VELOCITY, _velocity);
+}
+
+ALfloat* OpenALPlayer::getVelocity() {
+ return _velocity;
+}
+
+/**
+* Set direction of sound source in coordinate system
+*/
+void OpenALPlayer::setDirection(ALfloat direction[]) {
+ memcpy(&_direction, direction, 3 * sizeof(ALfloat));
+ if (_isInitialized)
+ alSourcefv(_alId, AL_DIRECTION, _direction);
+}
+
+ALfloat* OpenALPlayer::getDirection() {
+ return _direction;
+}
+
+void OpenALPlayer::setBufferSize(int bufferSize) {
+ if (_bufferSize > 0)
+ throw std::runtime_error("cannot modify buffersize");
+ _bufferSize = bufferSize;
+}
+
+int OpenALPlayer::getBufferSize() {
+ return _bufferSize;
+}
+
+OpenALPlayerCallback* OpenALPlayer::getCallback() {
+ return _audioCallback;
+}
+void OpenALPlayer::setCallback(OpenALPlayerCallback* callback) {
+ _audioCallback = callback;
+}
+
+void* OpenALPlayer::getUserData() {
+ return _userData;
+}
+void OpenALPlayer::setUserData(void* userData) {
+ _userData = userData;
+}