Use a background thread for processing OpenAL streams

This commit is contained in:
Chris Robinson 2016-04-26 06:12:56 -07:00
parent 204d0c8572
commit 6e25d3f618
6 changed files with 145 additions and 86 deletions

View file

@ -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;

View file

@ -2605,17 +2605,6 @@ void S_StopMusic (bool force)
}
}
//==========================================================================
//
//
//
//==========================================================================
void S_UpdateMusic()
{
GSnd->UpdateMusic();
}
//==========================================================================
//
// CCMD playsound

View file

@ -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);

View file

@ -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;

View file

@ -218,7 +218,7 @@ class OpenALSoundStream : public SoundStream
ALuint Buffers[BufferCount];
ALuint Source;
bool Playing;
std::atomic<bool> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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);
}
return !Playing.load();
}
// 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;
}
FString GetStats()
virtual FString GetStats()
{
FString stats;
size_t pos, len;
@ -469,6 +433,7 @@ public:
ALint state;
ALenum err;
std::unique_lock<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;

View file

@ -1,6 +1,11 @@
#ifndef OALSOUND_H
#define OALSOUND_H
#include <thread>
#include <mutex>
#include <atomic>
#include <condition_variable>
#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<bool> QuitThread;
ALCdevice *Device;
ALCcontext *Context;