diff --git a/src/d_main.cpp b/src/d_main.cpp index 84e03cdaa8..4c0d2537dc 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -913,7 +913,6 @@ void D_Display () } while (diff < 1); wipestart = nowtime; done = screen->WipeDo (1); - S_UpdateMusic(); // OpenAL needs this to keep the music running, thanks to a complete lack of a sane streaming implementation using callbacks. :( C_DrawConsole (hw2d); // console and M_Drawer (); // menu are drawn even on top of wipes screen->Update (); // page flip or blit buffer @@ -1012,7 +1011,6 @@ void D_DoomLoop () // Update display, next frame, with current state. I_StartTic (); D_Display (); - S_UpdateMusic(); // OpenAL needs this to keep the music running, thanks to a complete lack of a sane streaming implementation using callbacks. :( if (wantToRestart) { wantToRestart = false; diff --git a/src/s_sound.cpp b/src/s_sound.cpp index ac4dbc78fd..8cafa4d6ac 100644 --- a/src/s_sound.cpp +++ b/src/s_sound.cpp @@ -2605,17 +2605,6 @@ void S_StopMusic (bool force) } } -//========================================================================== -// -// -// -//========================================================================== - -void S_UpdateMusic() -{ - GSnd->UpdateMusic(); -} - //========================================================================== // // CCMD playsound diff --git a/src/s_sound.h b/src/s_sound.h index ad06195e17..a3a1a39f30 100644 --- a/src/s_sound.h +++ b/src/s_sound.h @@ -329,7 +329,6 @@ int S_GetMusic (char **name); // Stops the music for sure. void S_StopMusic (bool force); -void S_UpdateMusic(); // Stop and resume music, during game PAUSE. void S_PauseSound (bool notmusic, bool notsfx); diff --git a/src/sound/i_sound.h b/src/sound/i_sound.h index 836d620191..426dcadbd8 100644 --- a/src/sound/i_sound.h +++ b/src/sound/i_sound.h @@ -147,7 +147,6 @@ public: virtual void UpdateListener (SoundListener *) = 0; virtual void UpdateSounds () = 0; - virtual void UpdateMusic() {} virtual bool IsValid () = 0; virtual void PrintStatus () = 0; diff --git a/src/sound/oalsound.cpp b/src/sound/oalsound.cpp index 9b7de9e43c..5604467329 100644 --- a/src/sound/oalsound.cpp +++ b/src/sound/oalsound.cpp @@ -218,7 +218,7 @@ class OpenALSoundStream : public SoundStream ALuint Buffers[BufferCount]; ALuint Source; - bool Playing; + std::atomic Playing; bool Looping; ALfloat Volume; @@ -282,12 +282,14 @@ public: OpenALSoundStream(OpenALSoundRenderer *renderer) : Renderer(renderer), Source(0), Playing(false), Looping(false), Volume(1.0f), Reader(NULL), Decoder(NULL) { - Renderer->Streams.Push(this); memset(Buffers, 0, sizeof(Buffers)); + Renderer->AddStream(this); } virtual ~OpenALSoundStream() { + Renderer->RemoveStream(this); + if(Source) { alSourceRewind(Source); @@ -304,9 +306,6 @@ public: } getALError(); - Renderer->Streams.Delete(Renderer->Streams.Find(this)); - Renderer = NULL; - delete Decoder; delete Reader; } @@ -316,7 +315,7 @@ public: { SetVolume(vol); - if(Playing) + if(Playing.load()) return true; /* Clear the buffer queue, then fill and queue each buffer */ @@ -337,21 +336,24 @@ public: return false; alSourcePlay(Source); - Playing = (getALError()==AL_NO_ERROR); + if(getALError() != AL_NO_ERROR) + return false; - return Playing; + Playing.store(true); + return true; } virtual void Stop() { - if(!Playing) + if(!Playing.load()) return; + std::unique_lock lock(Renderer->StreamLock); alSourceStop(Source); alSourcei(Source, AL_BUFFER, 0); getALError(); - Playing = false; + Playing.store(false); } virtual void SetVolume(float vol) @@ -377,21 +379,25 @@ public: virtual bool SetPosition(unsigned int ms_pos) { + std::unique_lock lock(Renderer->StreamLock); if(!Decoder->seek(ms_pos)) return false; - if(!Playing) + if(!Playing.load()) return true; - // Stop the source so that all buffers become processed, then call - // IsEnded() to refill and restart the source queue with the new + // Stop the source so that all buffers become processed, which will + // allow the next update to restart the source queue with the new // position. alSourceStop(Source); getALError(); - return !IsEnded(); + lock.unlock(); + Renderer->StreamWake.notify_all(); + return true; } virtual unsigned int GetPosition() { + std::unique_lock lock(Renderer->StreamLock); ALint offset, queued, state; alGetSourcei(Source, AL_SAMPLE_OFFSET, &offset); alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued); @@ -400,6 +406,8 @@ public: return 0; size_t pos = Decoder->getSampleOffset(); + lock.unlock(); + if(state != AL_STOPPED) { size_t rem = queued*(Data.Size()/FrameSize) - offset; @@ -411,54 +419,10 @@ public: virtual bool IsEnded() { - if(!Playing) - return true; - - ALint state, processed; - alGetSourcei(Source, AL_SOURCE_STATE, &state); - alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed); - - Playing = (getALError()==AL_NO_ERROR); - if(!Playing) - return true; - - // For each processed buffer in the queue... - while(processed > 0) - { - ALuint bufid; - - // Unqueue the oldest buffer, fill it with more data, and queue it - // on the end - alSourceUnqueueBuffers(Source, 1, &bufid); - processed--; - - if(Callback(this, &Data[0], Data.Size(), UserData)) - { - alBufferData(bufid, Format, &Data[0], Data.Size(), SampleRate); - alSourceQueueBuffers(Source, 1, &bufid); - } - } - - // If the source is not playing or paused, and there are buffers queued, - // then there was an underrun. Restart the source. - Playing = (getALError()==AL_NO_ERROR); - if(Playing && state != AL_PLAYING && state != AL_PAUSED) - { - ALint queued = 0; - alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued); - - Playing = (getALError() == AL_NO_ERROR) && (queued > 0); - if(Playing) - { - alSourcePlay(Source); - Playing = (getALError()==AL_NO_ERROR); - } - } - - return !Playing; + return !Playing.load(); } - FString GetStats() + virtual FString GetStats() { FString stats; size_t pos, len; @@ -469,6 +433,7 @@ public: ALint state; ALenum err; + std::unique_lock lock(Renderer->StreamLock); alGetSourcef(Source, AL_GAIN, &volume); alGetSourcei(Source, AL_SAMPLE_OFFSET, &offset); alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed); @@ -476,16 +441,19 @@ public: alGetSourcei(Source, AL_SOURCE_STATE, &state); if((err=alGetError()) != AL_NO_ERROR) { + lock.unlock(); stats = "Error getting stats: "; stats += alGetString(err); return stats; } + pos = Decoder->getSampleOffset(); + len = Decoder->getSampleLength(); + lock.unlock(); + stats = (state == AL_INITIAL) ? "Buffering" : (state == AL_STOPPED) ? "Underrun" : (state == AL_PLAYING || state == AL_PAUSED) ? "Ready" : "Unknown state"; - pos = Decoder->getSampleOffset(); - len = Decoder->getSampleLength(); if(state == AL_STOPPED) offset = BufferCount * (Data.Size()/FrameSize); else @@ -511,6 +479,57 @@ public: return stats; } + bool Process() + { + if(!Playing.load()) + return false; + + ALint state, processed; + alGetSourcei(Source, AL_SOURCE_STATE, &state); + alGetSourcei(Source, AL_BUFFERS_PROCESSED, &processed); + if(getALError() != AL_NO_ERROR) + { + Playing.store(false); + return false; + } + + // For each processed buffer in the queue... + while(processed > 0) + { + ALuint bufid; + + // Unqueue the oldest buffer, fill it with more data, and queue it + // on the end + alSourceUnqueueBuffers(Source, 1, &bufid); + processed--; + + if(Callback(this, &Data[0], Data.Size(), UserData)) + { + alBufferData(bufid, Format, &Data[0], Data.Size(), SampleRate); + alSourceQueueBuffers(Source, 1, &bufid); + } + } + + // If the source is not playing or paused, and there are buffers queued, + // then there was an underrun. Restart the source. + bool ok = (getALError()==AL_NO_ERROR); + if(ok && state != AL_PLAYING && state != AL_PAUSED) + { + ALint queued = 0; + alGetSourcei(Source, AL_BUFFERS_QUEUED, &queued); + + ok = (getALError() == AL_NO_ERROR) && (queued > 0); + if(ok) + { + alSourcePlay(Source); + ok = (getALError()==AL_NO_ERROR); + } + } + + Playing.store(ok); + return ok; + } + bool Init(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata) { if(!SetupSource()) @@ -688,7 +707,7 @@ static void LoadALFunc(const char *name, T *x) #define LOAD_FUNC(x) (LoadALFunc(#x, &x)) OpenALSoundRenderer::OpenALSoundRenderer() - : Device(NULL), Context(NULL), SFXPaused(0), PrevEnvironment(NULL), EnvSlot(0) + : QuitThread(false), Device(NULL), Context(NULL), SFXPaused(0), PrevEnvironment(NULL), EnvSlot(0) { EnvFilters[0] = EnvFilters[1] = 0; @@ -912,6 +931,15 @@ OpenALSoundRenderer::~OpenALSoundRenderer() if(!Device) return; + if(StreamThread.joinable()) + { + std::unique_lock lock(StreamLock); + QuitThread.store(true); + lock.unlock(); + StreamWake.notify_all(); + StreamThread.join(); + } + while(Streams.Size() > 0) delete Streams[0]; @@ -946,6 +974,43 @@ OpenALSoundRenderer::~OpenALSoundRenderer() Device = NULL; } +void OpenALSoundRenderer::BackgroundProc() +{ + std::unique_lock lock(StreamLock); + while(!QuitThread.load()) + { + if(Streams.Size() == 0) + { + // If there's nothing to play, wait indefinitely. + StreamWake.wait(lock); + } + else + { + // Else, process all active streams and sleep for 100ms + for(size_t i = 0;i < Streams.Size();i++) + Streams[i]->Process(); + StreamWake.wait_for(lock, std::chrono::milliseconds(100)); + } + } +} + +void OpenALSoundRenderer::AddStream(OpenALSoundStream *stream) +{ + std::unique_lock lock(StreamLock); + Streams.Push(stream); + lock.unlock(); + // There's a stream to play, make sure the background thread is aware + StreamWake.notify_all(); +} + +void OpenALSoundRenderer::RemoveStream(OpenALSoundStream *stream) +{ + std::unique_lock lock(StreamLock); + unsigned int idx = Streams.Find(stream); + if(idx < Streams.Size()) + Streams.Delete(idx); +} + void OpenALSoundRenderer::SetSfxVolume(float volume) { SfxVolume = volume; @@ -1172,6 +1237,8 @@ void OpenALSoundRenderer::UnloadSound(SoundHandle sfx) SoundStream *OpenALSoundRenderer::CreateStream(SoundStreamCallback callback, int buffbytes, int flags, int samplerate, void *userdata) { + if(StreamThread.get_id() == std::thread::id()) + StreamThread = std::thread(std::mem_fn(&OpenALSoundRenderer::BackgroundProc), this); OpenALSoundStream *stream = new OpenALSoundStream(this); if (!stream->Init(callback, buffbytes, flags, samplerate, userdata)) { @@ -1183,6 +1250,8 @@ SoundStream *OpenALSoundRenderer::CreateStream(SoundStreamCallback callback, int SoundStream *OpenALSoundRenderer::OpenStream(FileReader *reader, int flags) { + if(StreamThread.get_id() == std::thread::id()) + StreamThread = std::thread(std::mem_fn(&OpenALSoundRenderer::BackgroundProc), this); OpenALSoundStream *stream = new OpenALSoundStream(this); if (!stream->Init(reader, !!(flags&SoundStream::Loop))) { @@ -1741,13 +1810,6 @@ void OpenALSoundRenderer::UpdateSounds() PurgeStoppedSources(); } -void OpenALSoundRenderer::UpdateMusic() -{ - // For some reason this isn't being called? - for(uint32 i = 0;i < Streams.Size();++i) - Streams[i]->IsEnded(); -} - bool OpenALSoundRenderer::IsValid() { return Device != NULL; diff --git a/src/sound/oalsound.h b/src/sound/oalsound.h index f528dbc6ad..525078dc61 100644 --- a/src/sound/oalsound.h +++ b/src/sound/oalsound.h @@ -1,6 +1,11 @@ #ifndef OALSOUND_H #define OALSOUND_H +#include +#include +#include +#include + #include "i_sound.h" #include "s_sound.h" #include "menu/menu.h" @@ -109,7 +114,6 @@ public: virtual void UpdateListener(SoundListener *); virtual void UpdateSounds(); - virtual void UpdateMusic(); virtual void MarkStartTime(FISoundChannel*); virtual float GetAudibility(FISoundChannel*); @@ -174,10 +178,18 @@ private: ALvoid (AL_APIENTRY*alDeferUpdatesSOFT)(void); ALvoid (AL_APIENTRY*alProcessUpdatesSOFT)(void); + void BackgroundProc(); + void AddStream(OpenALSoundStream *stream); + void RemoveStream(OpenALSoundStream *stream); void LoadReverb(const ReverbContainer *env); void PurgeStoppedSources(); static FSoundChan *FindLowestChannel(); + std::thread StreamThread; + std::mutex StreamLock; + std::condition_variable StreamWake; + std::atomic QuitThread; + ALCdevice *Device; ALCcontext *Context;