From f953f2b5abd2c7782b03b93dab63bc1d13b1ca6e Mon Sep 17 00:00:00 2001 From: fgenesis Date: Fri, 25 May 2012 17:38:59 +0200 Subject: [PATCH] Do all sound decoding in *one* background thread, instead of one thread per decoder. --- BBGE/FmodOpenALBridge.cpp | 213 +++++++++++++++++++++++++------------- BBGE/MT.cpp | 75 ++++++++++++++ BBGE/MT.h | 117 +++++++++++++++++++++ CMakeLists.txt | 1 + 4 files changed, 333 insertions(+), 73 deletions(-) create mode 100644 BBGE/MT.cpp create mode 100644 BBGE/MT.h diff --git a/BBGE/FmodOpenALBridge.cpp b/BBGE/FmodOpenALBridge.cpp index 820e8dd..cb33f9a 100644 --- a/BBGE/FmodOpenALBridge.cpp +++ b/BBGE/FmodOpenALBridge.cpp @@ -42,6 +42,8 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "ogg/ogg.h" #include "vorbis/vorbisfile.h" +#include "MT.h" + #ifndef _DEBUG //#define _DEBUG 1 #endif @@ -79,7 +81,13 @@ public: static int mem_seek(void *datasource, ogg_int64_t offset, int whence); static long mem_tell(void *datasource); + static void startDecoderThread(); + static void stopDecoderThread(); + private: + + void _stop(); + // Decoding loop, run in a separate thread (if threads are available). static void decode_loop(OggDecoder *this_); @@ -107,19 +115,27 @@ private: ALenum format; int freq; -#ifdef BBGE_BUILD_SDL - SDL_Thread *thread; -#else - #warning Threads not supported, music may cut out on area changes! - // ... because the stream runs out of decoded data while the area is - // still loading, so OpenAL aborts playback. -#endif - volatile bool stop_thread; + bool thread; // true if played by background thread bool playing; bool loop; bool eof; // End of file _or_ unrecoverable error encountered + bool stopped; // true if enqueued deletion unsigned int samples_done; // Number of samples played and dequeued + +#ifdef BBGE_BUILD_SDL + static SDL_Thread *decoderThread; + static LockedQueue decoderQ; + static volatile bool stop_thread; + static std::list decoderList; // used by decoder thread only + +#else +#warning Threads not supported, music may cut out on area changes! +// ... because the stream runs out of decoded data while the area is +// still loading, so OpenAL aborts playback. +#endif + + static void detachDecoder(OggDecoder *); }; // File I/O callback set (OV_CALLBACKS_NOCLOSE from libvorbis 1.2.0). @@ -155,6 +171,88 @@ static const ov_callbacks ogg_memory_callbacks = { OggDecoder::mem_tell }; +#ifdef BBGE_BUILD_SDL +SDL_Thread *OggDecoder::decoderThread = NULL; +LockedQueue OggDecoder::decoderQ; +volatile bool OggDecoder::stop_thread; +std::list OggDecoder::decoderList; +#endif + +void OggDecoder::startDecoderThread() +{ +#ifdef BBGE_BUILD_SDL + stop_thread = false; + decoderThread = SDL_CreateThread((int (*)(void *))decode_loop, NULL); + if (!decoderThread) + { + debugLog("Failed to create Ogg Vorbis decode thread: " + + std::string(SDL_GetError())); + } +#endif +} + +void OggDecoder::stopDecoderThread() +{ +#ifdef BBGE_BUILD_SDL + if (decoderThread) + { + stop_thread = true; + debugLog("Waiting for decoder thread to exit..."); + SDL_WaitThread(decoderThread, NULL); + decoderThread = NULL; + } +#endif +} + +void OggDecoder::detachDecoder(OggDecoder *ogg) +{ +#ifdef BBGE_BUILD_SDL + if(decoderThread) + { + ogg->thread = true; + decoderQ.push(ogg); + } +#endif +} + +void OggDecoder::decode_loop(OggDecoder *this_) +{ + while (!this_->stop_thread) + { +#ifdef BBGE_BUILD_SDL + SDL_Delay(10); +#endif + // Transfer decoder to this background thread + OggDecoder *ogg; + while(decoderQ.pop(ogg)) + decoderList.push_back(ogg); + + for(std::list::iterator it = decoderList.begin(); it != decoderList.end(); ) + { + ogg = *it; + if (ogg->playing) + { + int processed = 0; + alGetSourcei(ogg->source, AL_BUFFERS_PROCESSED, &processed); + for (int i = 0; i < processed; i++) + { + ogg->samples_done += BUFFER_LENGTH; + ALuint buffer = 0; + alSourceUnqueueBuffers(ogg->source, 1, &buffer); + if (buffer) + ogg->queue(buffer); + } + ++it; + } + else + { + delete ogg; + decoderList.erase(it++); + } + } + } +} + OggDecoder::OggDecoder(FILE *fp) { @@ -167,14 +265,12 @@ OggDecoder::OggDecoder(FILE *fp) this->data = NULL; this->data_size = 0; this->data_pos = 0; -#ifdef BBGE_BUILD_SDL - this->thread = NULL; -#endif - this->stop_thread = true; + this->thread = false; this->playing = false; this->loop = false; this->eof = false; this->samples_done = 0; + this->stopped = false; } OggDecoder::OggDecoder(const void *data, long data_size) @@ -188,20 +284,17 @@ OggDecoder::OggDecoder(const void *data, long data_size) this->data = (const char *)data; this->data_size = data_size; this->data_pos = 0; -#ifdef BBGE_BUILD_SDL - this->thread = NULL; -#endif - this->stop_thread = true; + this->thread = false; this->playing = false; this->loop = false; this->eof = false; this->samples_done = 0; + this->stopped = false; } OggDecoder::~OggDecoder() { - if (playing) - stop(); + _stop(); for (int i = 0; i < NUM_BUFFERS; i++) { @@ -280,27 +373,15 @@ bool OggDecoder::start(ALuint source, bool loop) for (int i = 0; i < NUM_BUFFERS; i++) queue(buffers[i]); -#ifdef BBGE_BUILD_SDL - stop_thread = false; - thread = SDL_CreateThread((int (*)(void *))decode_loop, this); - if (!thread) - { - debugLog("Failed to create Ogg Vorbis decode thread: " - + std::string(SDL_GetError())); - } -#endif + detachDecoder(this); return true; } void OggDecoder::update() { - if (!playing) + if (!playing || thread) return; -#ifdef BBGE_BUILD_SDL - if (thread) - return; -#endif int processed = 0; alGetSourcei(source, AL_BUFFERS_PROCESSED, &processed); @@ -315,17 +396,18 @@ void OggDecoder::update() void OggDecoder::stop() { - if (!playing) - return; - -#ifdef BBGE_BUILD_SDL if (thread) - { - stop_thread = true; - SDL_WaitThread(thread, NULL); - thread = NULL; - } -#endif + playing = false; // The background thread will take care of deletion then. + else + delete this; +} + +void OggDecoder::_stop() +{ + playing = false; + + if (stopped) + return; ov_clear(&vf); @@ -342,6 +424,7 @@ void OggDecoder::stop() alDeleteBuffers(1, &buffers[i]); buffers[i] = 0; } + stopped = true; } double OggDecoder::position() @@ -352,27 +435,6 @@ double OggDecoder::position() return (double)samples_played / (double)freq; } -void OggDecoder::decode_loop(OggDecoder *this_) -{ - while (!this_->stop_thread) - { -#ifdef BBGE_BUILD_SDL - SDL_Delay(10); -#endif - - int processed = 0; - alGetSourcei(this_->source, AL_BUFFERS_PROCESSED, &processed); - for (int i = 0; i < processed; i++) - { - this_->samples_done += BUFFER_LENGTH; - ALuint buffer = 0; - alSourceUnqueueBuffers(this_->source, 1, &buffer); - if (buffer) - this_->queue(buffer); - } - } -} - #if (defined(BBGE_BUILD_SDL) && (SDL_BYTEORDER == SDL_BIG_ENDIAN)) #define BBGE_BIGENDIAN 1 #else @@ -675,16 +737,16 @@ void OpenALChannel::setGroupVolume(const float _volume) bool OpenALChannel::start(OpenALSound *sound) { if (decoder) - delete decoder; + delete decoder; if (sound->getFile()) - decoder = new OggDecoder(sound->getFile()); + decoder = new OggDecoder(sound->getFile()); else - decoder = new OggDecoder(sound->getData(), sound->getSize()); + decoder = new OggDecoder(sound->getData(), sound->getSize()); if (!decoder->start(sid, sound->isLooping())) { - delete decoder; - decoder = NULL; - return false; + delete decoder; + decoder = NULL; + return false; } return true; } @@ -693,8 +755,8 @@ void OpenALChannel::update() { if (inuse) { - if (decoder) - decoder->update(); + if (decoder) + decoder->update(); ALint state = 0; alGetSourceiv(sid, AL_SOURCE_STATE, &state); SANITY_CHECK_OPENAL_CALL(); @@ -967,8 +1029,8 @@ FMOD_RESULT OpenALChannel::stop() { if (decoder) { - delete decoder; - decoder = NULL; + decoder->stop(); + decoder = NULL; } alSourceStop(sid); SANITY_CHECK_OPENAL_CALL(); @@ -1248,6 +1310,9 @@ FMOD_RESULT OpenALSystem::init(int maxchannels, const FMOD_INITFLAGS flags, cons std::stringstream ss; ss << "Using " << num_channels << " sound channels."; debugLog(ss.str()); + + OggDecoder::startDecoderThread(); + return FMOD_OK; } @@ -1301,6 +1366,8 @@ FMOD_RESULT OpenALSystem::playSound(FMOD_CHANNELINDEX channelid, Sound *_sound, ALBRIDGE(System,release,(),()) FMOD_RESULT OpenALSystem::release() { + OggDecoder::stopDecoderThread(); + ALCcontext *ctx = alcGetCurrentContext(); if (ctx) { diff --git a/BBGE/MT.cpp b/BBGE/MT.cpp new file mode 100644 index 0000000..da2e766 --- /dev/null +++ b/BBGE/MT.cpp @@ -0,0 +1,75 @@ +#include "MT.h" +#include "Base.h" + +#ifdef BBGE_BUILD_SDL + +// --------- Lockable ---------- + +Lockable::Lockable() +: _mtx(NULL) +{ +#ifdef BBGE_BUILD_SDL + _mtx = SDL_CreateMutex(); +#endif +} + +Lockable::~Lockable() +{ +#ifdef BBGE_BUILD_SDL + SDL_DestroyMutex((SDL_mutex*)_mtx); +#endif +} + +void Lockable::lock() +{ +#ifdef BBGE_BUILD_SDL + SDL_LockMutex((SDL_mutex*)_mtx); +#endif +} + +void Lockable::unlock() +{ +#ifdef BBGE_BUILD_SDL + SDL_UnlockMutex((SDL_mutex*)_mtx); +#endif +} + +// --------- Waitable ---------- + +Waitable::Waitable() +: _cond(NULL) +{ +#ifdef BBGE_BUILD_SDL + _cond = SDL_CreateCond(); +#endif +} + +Waitable::~Waitable() +{ +#ifdef BBGE_BUILD_SDL + SDL_DestroyCond((SDL_cond*)_cond); +#endif +} + +void Waitable::wait() +{ +#ifdef BBGE_BUILD_SDL + SDL_CondWait((SDL_cond*)_cond, (SDL_mutex*)mutex()); +#endif +} + +void Waitable::signal() +{ +#ifdef BBGE_BUILD_SDL + SDL_CondSignal((SDL_cond*)_cond); +#endif +} + +void Waitable::broadcast() +{ +#ifdef BBGE_BUILD_SDL + SDL_CondBroadcast((SDL_cond*)_cond); +#endif +} + +#endif // BBGE_BUILD_SDL diff --git a/BBGE/MT.h b/BBGE/MT.h new file mode 100644 index 0000000..cd915fe --- /dev/null +++ b/BBGE/MT.h @@ -0,0 +1,117 @@ +#ifndef BBGE_MT_H +#define BBGE_MT_H + +#include +#include +#include + +class Lockable +{ +public: + Lockable(); + virtual ~Lockable(); + void lock(); + void unlock(); + +protected: + inline void *mutex() { return _mtx; } + +private: + void *_mtx; +}; + +class Waitable : public Lockable +{ +public: + Waitable(); + virtual ~Waitable(); + void wait(); // releases the associated lock while waiting + void signal(); // signal a single waiting thread + void broadcast(); // signal all waiting threads + +private: + void *_cond; +}; + +class MTGuard +{ +public: + MTGuard(Lockable& x) : _obj(&x) { _obj->lock(); } + MTGuard(Lockable* x) : _obj(x) { _obj->lock(); } + ~MTGuard() { _obj->unlock(); } + Lockable *_obj; +}; + +template class BlockingQueue : public Waitable +{ +public: + void push(const T& e) + { + lock(); + _q.push(e); + unlock(); + signal(); + } + bool pop(T& e) // blocks if empty + { + lock(); + while(_q.empty()) + wait(); + e = _q.front(); + _q.pop(); + unlock(); + return true; + } +private: + std::queue _q; +}; + +template class LockedQueue : public Lockable +{ +public: + void push(const T& e) + { + lock(); + _q.push(e); + unlock(); + } + bool pop(T& e) // continues if empty + { + lock(); + if(_q.empty()) + { + unlock(); + return false; + } + e = _q.front(); + _q.pop(); + unlock(); + return true; + } + bool empty() + { + lock(); + bool e = _q.empty(); + unlock(); + return e; + } + bool popIfPossible(T& e) + { + lock(); + if(!_q.empty()) + { + e = _q.front(); + _q.pop(); + unlock(); + return true; + } + unlock(); + return false; + } + +private: + std::queue _q; +}; + + +#endif diff --git a/CMakeLists.txt b/CMakeLists.txt index 380819f..5a93715 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -382,6 +382,7 @@ SET(BBGE_SRCS ${BBGEDIR}/Joystick.cpp ${BBGEDIR}/LensFlare.cpp ${BBGEDIR}/Math.cpp + ${BBGEDIR}/MT.cpp ${BBGEDIR}/ParticleEffect.cpp ${BBGEDIR}/ParticleManager.cpp ${BBGEDIR}/Particles.cpp