diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp index ed73e940..c46f836c 100644 --- a/src/audio/oal/stream.cpp +++ b/src/audio/oal/stream.cpp @@ -1,8 +1,6 @@ #include "common.h" #ifdef AUDIO_OAL -#include "stream.h" -#include "sampman.h" #if defined _MSC_VER && !defined CMAKE_NO_AUTOLINK #ifdef AUDIO_OAL_USE_SNDFILE @@ -22,6 +20,28 @@ #include #endif +#include +#include + +#ifdef MULTITHREADED_AUDIO +#include +#include +#include +#include +#include "MusicManager.h" +#include "stream.h" + +std::thread gAudioThread; +std::mutex gAudioThreadQueueMutex; +std::condition_variable gAudioThreadCv; +bool gAudioThreadTerm = false; +std::queue gStreamsToProcess; // values are not unique, we will handle that ourself +#else +#include "stream.h" +#endif + +#include "sampman.h" + #ifndef _WIN32 #include "crossplatform.h" #endif @@ -39,6 +59,10 @@ class CSortStereoBuffer { uint16* PcmBuf; size_t BufSize; +//#ifdef MULTITHREADED_AUDIO +// std::mutex Mutex; +//#endif + public: CSortStereoBuffer() : PcmBuf(nil), BufSize(0) {} ~CSortStereoBuffer() @@ -65,6 +89,9 @@ public: void SortStereo(void* buf, size_t size) { +//#ifdef MULTITHREADED_AUDIO +// std::lock_guard lock(Mutex); +//#endif uint16* InBuf = (uint16*)buf; uint16* OutBuf = GetBuffer(size); @@ -279,6 +306,10 @@ public: #undef CLOSE_ON_ERROR } + void FileOpen() + { + } + ~CWavFile() { Close(); @@ -289,6 +320,7 @@ public: return m_bIsOpen; } + uint32 GetSampleSize() { return sizeof(uint16); @@ -405,6 +437,10 @@ public: m_pfSound = sf_open(path, SFM_READ, &m_soundInfo); } + void FileOpen() + { + } + ~CSndFile() { if ( m_pfSound ) @@ -464,8 +500,6 @@ public: #endif #ifdef AUDIO_OAL_USE_MPG123 -// fuzzy seek eliminates stutter when playing ADF but spams errors a lot (and breaks radio sometimes) -//#define MP3_USE_FUZZY_SEEK class CMP3File : public IDecoder { @@ -474,45 +508,58 @@ protected: bool m_bOpened; uint32 m_nRate; uint32 m_nChannels; - + const char* m_pPath; + bool m_bFileNotOpenedYet; + CMP3File() : m_pMH(nil), m_bOpened(false), m_nRate(0), + m_bFileNotOpenedYet(false), m_nChannels(0) {} public: CMP3File(const char *path) : m_pMH(nil), m_bOpened(false), m_nRate(0), - m_nChannels(0) + m_nChannels(0), + m_pPath(path), + m_bFileNotOpenedYet(false) { m_pMH = mpg123_new(nil, nil); if ( m_pMH ) { -#ifdef MP3_USE_FUZZY_SEEK - mpg123_param(m_pMH, MPG123_FLAGS, MPG123_FUZZY | MPG123_SEEKBUFFER | MPG123_GAPLESS | MPG123_QUIET, 0.0); -#else mpg123_param(m_pMH, MPG123_FLAGS, MPG123_SEEKBUFFER | MPG123_GAPLESS, 0.0); -#endif - long rate = 0; - int channels = 0; - int encoding = 0; - - m_bOpened = mpg123_open(m_pMH, path) == MPG123_OK - && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK; - m_nRate = rate; - m_nChannels = channels; - - if ( IsOpened() ) - { - mpg123_format_none(m_pMH); - mpg123_format(m_pMH, rate, channels, encoding); - } + m_bOpened = true; + m_bFileNotOpenedYet = true; + // It's possible to move this to audioFileOpsThread(), but effect isn't noticable + probably not compatible with our current cutscene audio handling +#if 1 + FileOpen(); +#endif } } + void FileOpen() + { + if(!m_bFileNotOpenedYet) return; + + long rate = 0; + int channels = 0; + int encoding = 0; + m_bOpened = mpg123_open(m_pMH, m_pPath) == MPG123_OK + && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK; + + m_nRate = rate; + m_nChannels = channels; + + if(IsOpened()) { + mpg123_format_none(m_pMH); + mpg123_format(m_pMH, rate, channels, encoding); + } + m_bFileNotOpenedYet = false; + } + ~CMP3File() { if ( m_pMH ) @@ -535,7 +582,7 @@ public: uint32 GetSampleCount() { - if ( !IsOpened() ) return 0; + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; return mpg123_length(m_pMH); } @@ -551,19 +598,19 @@ public: void Seek(uint32 milliseconds) { - if ( !IsOpened() ) return; + if ( !IsOpened() || m_bFileNotOpenedYet ) return; mpg123_seek(m_pMH, ms2samples(milliseconds), SEEK_SET); } uint32 Tell() { - if ( !IsOpened() ) return 0; + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; return samples2ms(mpg123_tell(m_pMH)); } uint32 Decode(void *buffer) { - if ( !IsOpened() ) return 0; + if ( !IsOpened() || m_bFileNotOpenedYet ) return 0; size_t size; int err = mpg123_read(m_pMH, (unsigned char *)buffer, GetBufferSize(), &size); @@ -602,29 +649,42 @@ public: m_pMH = mpg123_new(nil, nil); if (m_pMH) { -#ifdef MP3_USE_FUZZY_SEEK - mpg123_param(m_pMH, MPG123_FLAGS, MPG123_FUZZY | MPG123_SEEKBUFFER | MPG123_GAPLESS | MPG123_QUIET, 0.0); -#else mpg123_param(m_pMH, MPG123_FLAGS, MPG123_SEEKBUFFER | MPG123_GAPLESS, 0.0); + + m_bOpened = true; + m_bFileNotOpenedYet = true; + m_pPath = path; + // It's possible to move this to audioFileOpsThread(), but effect isn't noticable + probably not compatible with our current cutscene audio handling +#if 1 + FileOpen(); #endif - long rate = 0; - int channels = 0; - int encoding = 0; - FILE* f = fopen(path, "rb"); - - m_bOpened = mpg123_replace_reader_handle(m_pMH, r_read, r_seek, r_close) == MPG123_OK - && mpg123_open_handle(m_pMH, f) == MPG123_OK && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK; - m_nRate = rate; - m_nChannels = channels; - - if (IsOpened()) - { - mpg123_format_none(m_pMH); - mpg123_format(m_pMH, rate, channels, encoding); - } } } + + void FileOpen() + { + if(!m_bFileNotOpenedYet) return; + + long rate = 0; + int channels = 0; + int encoding = 0; + + FILE *f = fopen(m_pPath, "rb"); + + m_bOpened = f && mpg123_replace_reader_handle(m_pMH, r_read, r_seek, r_close) == MPG123_OK + && mpg123_open_handle(m_pMH, f) == MPG123_OK && mpg123_getformat(m_pMH, &rate, &channels, &encoding) == MPG123_OK; + + m_nRate = rate; + m_nChannels = channels; + + if(IsOpened()) { + mpg123_format_none(m_pMH); + mpg123_format(m_pMH, rate, channels, encoding); + } + + m_bFileNotOpenedYet = false; + } }; #endif @@ -744,6 +804,10 @@ public: m_ppVagBuffers[i] = new uint8[VB_BLOCK_SIZE]; } + void FileOpen() + { + } + ~CVbFile() { if (m_pFile) @@ -896,6 +960,10 @@ public: m_bOpened = true; } } + + void FileOpen() + { + } ~COpusFile() { @@ -961,11 +1029,183 @@ public: }; #endif + +// For multi-thread: Someone always acquire stream's mutex before entering here +void +CStream::BuffersShouldBeFilled() +{ +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) { + std::queue> tempQueue; + for(int i = 0; i < NUM_STREAMBUFFERS / 2; i++) { + tempQueue.push(std::pair(m_alBuffers[i * 2], m_alBuffers[i * 2 + 1])); + } + m_fillBuffers.swap(tempQueue); + + FlagAsToBeProcessed(); + + m_bActive = true; // to allow Update() to queue the filled buffers & play + return; + } + std::queue>().swap(m_fillBuffers); +#endif + if ( FillBuffers() != 0 ) + { + SetPlay(true); + } +} + +// returns whether it's queued (not on multi-thread) +bool +CStream::BufferShouldBeFilledAndQueued(std::pair* bufs) +{ +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) + m_fillBuffers.push(*bufs); + else +#endif + { + ALuint alBuffers[2] = {(*bufs).first, (*bufs).second}; // left - right + if (FillBuffer(alBuffers)) { + alSourceQueueBuffers(m_pAlSources[0], 1, &alBuffers[0]); + alSourceQueueBuffers(m_pAlSources[1], 1, &alBuffers[1]); + return true; + } + } + return false; +} + +#ifdef MULTITHREADED_AUDIO +void +CStream::FlagAsToBeProcessed(bool close) +{ + if (!close && MusicManager.m_nMusicMode == MUSICMODE_CUTSCENE) + return; + + gAudioThreadQueueMutex.lock(); + gStreamsToProcess.push(this); + gAudioThreadQueueMutex.unlock(); + + gAudioThreadCv.notify_one(); +} + +extern CStream *aStream[]; +void audioFileOpsThread() +{ + std::queue m_streamsToDelete; + + do + { + CStream *stream; + { + // Just a semaphore + std::unique_lock queueMutex(gAudioThreadQueueMutex); + gAudioThreadCv.wait(queueMutex, [m_streamsToDelete] { return gStreamsToProcess.size() > 0 || m_streamsToDelete.size() > 0 || gAudioThreadTerm; }); + if (gAudioThreadTerm) + return; + + if (!gStreamsToProcess.empty()) { + stream = gStreamsToProcess.front(); + gStreamsToProcess.pop(); + } else { + // End of streams. Perform deleting streams + while(!m_streamsToDelete.empty()) { + CStream *stream = m_streamsToDelete.front(); + m_streamsToDelete.pop(); + if (stream->m_pSoundFile) { + delete stream->m_pSoundFile; + stream->m_pSoundFile = nil; + } + + if (stream->m_pBuffer) { + free(stream->m_pBuffer); + stream->m_pBuffer = nil; + } + delete stream; + } + continue; + } + } + + std::unique_lock lock(stream->m_mutex); + + std::pair buffers, *lastBufAddr; + bool insertBufsAfterCheck = false; + + do { + if (stream->m_nDeleteMe == 1) { + m_streamsToDelete.push(stream); + stream->m_nDeleteMe = 2; + break; + } else if (stream->m_nDeleteMe == 2) { + break; + } + + if (!stream->IsOpened()) + break; + + if (stream->m_bReset) + break; + + // We gave up this idea for now + /* + stream->m_pSoundFile->FileOpen(); + + // Deffered allocation, do it now + if (stream->m_pBuffer == nil) { + stream->m_pBuffer = malloc(stream->m_pSoundFile->GetBufferSize()); + ASSERT(stream->m_pBuffer != nil); + } + */ + + if (stream->m_bDoSeek) { + stream->m_bDoSeek = false; + int pos = stream->m_SeekPos; + lock.unlock(); + stream->m_pSoundFile->Seek(pos); + lock.lock(); + + continue; // let's do the checks again, make sure we didn't miss anything while Seeking + } + + if (insertBufsAfterCheck) { + stream->m_queueBuffers.push(buffers); + insertBufsAfterCheck = false; + } + + if (!stream->m_fillBuffers.empty()) { + lastBufAddr = &stream->m_fillBuffers.front(); + buffers = *lastBufAddr; + lock.unlock(); + + ALuint alBuffers[2] = {buffers.first, buffers.second}; // left - right + bool filled = stream->FillBuffer(alBuffers); + + lock.lock(); + + // Make sure queue isn't touched after we released mutex + if (!stream->m_fillBuffers.empty() && lastBufAddr == &stream->m_fillBuffers.front()) { + stream->m_fillBuffers.pop(); + if (filled) + insertBufsAfterCheck = true; // Also make sure stream's properties aren't changed. So make one more pass, and push it to m_queueBuffers only if it pass checks again. + } + } else + break; + + } while (true); + + } while(true); +} +#endif + void CStream::Initialise() { #ifdef AUDIO_OAL_USE_MPG123 mpg123_init(); #endif +#ifdef MULTITHREADED_AUDIO + gAudioThread = std::thread(audioFileOpsThread); +#endif } void CStream::Terminate() @@ -973,6 +1213,14 @@ void CStream::Terminate() #ifdef AUDIO_OAL_USE_MPG123 mpg123_exit(); #endif +#ifdef MULTITHREADED_AUDIO + gAudioThreadQueueMutex.lock(); + gAudioThreadTerm = true; + gAudioThreadQueueMutex.unlock(); + + gAudioThreadCv.notify_one(); + gAudioThread.join(); +#endif } CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate) : @@ -981,6 +1229,11 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU m_pBuffer(nil), m_bPaused(false), m_bActive(false), +#ifdef MULTITHREADED_AUDIO + m_nDeleteMe(false), + m_bDoSeek(false), + m_SeekPos(0), +#endif m_pSoundFile(nil), m_bReset(false), m_nVolume(0), @@ -1027,42 +1280,57 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU if ( IsOpened() ) { - m_pBuffer = malloc(m_pSoundFile->GetBufferSize()); - ASSERT(m_pBuffer!=nil); - - DEV("AvgSamplesPerSec: %d\n", m_pSoundFile->GetAvgSamplesPerSec()); - DEV("SampleCount: %d\n", m_pSoundFile->GetSampleCount()); - DEV("SampleRate: %d\n", m_pSoundFile->GetSampleRate()); - DEV("Channels: %d\n", m_pSoundFile->GetChannels()); - DEV("Buffer Samples: %d\n", m_pSoundFile->GetBufferSamples()); - DEV("Buffer sec: %f\n", (float(m_pSoundFile->GetBufferSamples()) / float(m_pSoundFile->GetChannels())/ float(m_pSoundFile->GetSampleRate()))); - DEV("Length MS: %02d:%02d\n", (m_pSoundFile->GetLength() / 1000) / 60, (m_pSoundFile->GetLength() / 1000) % 60); - + uint32 bufSize = m_pSoundFile->GetBufferSize(); + if(bufSize != 0) { // Otherwise it's deferred + m_pBuffer = malloc(bufSize); + ASSERT(m_pBuffer != nil); + + DEV("AvgSamplesPerSec: %d\n", m_pSoundFile->GetAvgSamplesPerSec()); + DEV("SampleCount: %d\n", m_pSoundFile->GetSampleCount()); + DEV("SampleRate: %d\n", m_pSoundFile->GetSampleRate()); + DEV("Channels: %d\n", m_pSoundFile->GetChannels()); + DEV("Buffer Samples: %d\n", m_pSoundFile->GetBufferSamples()); + DEV("Buffer sec: %f\n", (float(m_pSoundFile->GetBufferSamples()) / float(m_pSoundFile->GetChannels())/ float(m_pSoundFile->GetSampleRate()))); + DEV("Length MS: %02d:%02d\n", (m_pSoundFile->GetLength() / 1000) / 60, (m_pSoundFile->GetLength() / 1000) % 60); + } return; } } CStream::~CStream() { - Delete(); + assert(!IsOpened()); } -void CStream::Delete() +void CStream::Close() { +#ifdef MULTITHREADED_AUDIO + { + std::lock_guard lock(m_mutex); + + Stop(); + ClearBuffers(); + m_nDeleteMe = true; + // clearing buffer queues are not needed. after m_nDeleteMe set, this stream is ded + } + + FlagAsToBeProcessed(true); +#else Stop(); ClearBuffers(); - + if ( m_pSoundFile ) { delete m_pSoundFile; m_pSoundFile = nil; } - + if ( m_pBuffer ) { free(m_pBuffer); m_pBuffer = nil; } +#endif } bool CStream::HasSource() @@ -1086,6 +1354,14 @@ bool CStream::IsPlaying() alGetSourcei(m_pAlSources[1], AL_SOURCE_STATE, &sourceState[1]); if (sourceState[0] == AL_PLAYING || sourceState[1] == AL_PLAYING) return true; + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + // Streams are designed in such a way that m_fillBuffers and m_queueBuffers will be *always* filled if audio is playing, and mutex is acquired + if (!m_fillBuffers.empty() || !m_queueBuffers.emptyNts()) + return true; +#endif } return false; @@ -1160,8 +1436,24 @@ void CStream::SetPan(uint8 nPan) void CStream::SetPosMS(uint32 nPos) { if ( !IsOpened() ) return; - m_pSoundFile->Seek(nPos); + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + std::queue>().swap(m_fillBuffers); + tsQueue>().swapNts(m_queueBuffers); // TSness not required, second thread always access it when stream mutex acquired + + if (MusicManager.m_nMusicMode != MUSICMODE_CUTSCENE) { + m_bDoSeek = true; + m_SeekPos = nPos; + } else +#endif + { + m_pSoundFile->Seek(nPos); + } ClearBuffers(); + + // adding to gStreamsToProcess not needed, someone always calls Start() / BuffersShouldBeFilled() after SetPosMS } uint32 CStream::GetPosMS() @@ -1169,10 +1461,16 @@ uint32 CStream::GetPosMS() if ( !HasSource() ) return 0; if ( !IsOpened() ) return 0; + // Deferred init causes division by zero + if (m_pSoundFile->GetChannels() == 0) + return 0; + ALint offset; //alGetSourcei(m_alSource, AL_SAMPLE_OFFSET, &offset); alGetSourcei(m_pAlSources[0], AL_BYTE_OFFSET, &offset); + //std::lock_guard lock(m_mutex); + return m_pSoundFile->Tell() - m_pSoundFile->samples2ms(m_pSoundFile->GetBufferSamples() * (NUM_STREAMBUFFERS/2-1)) / m_pSoundFile->GetChannels() + m_pSoundFile->samples2ms(offset/m_pSoundFile->GetSampleSize()) / m_pSoundFile->GetChannels(); @@ -1186,6 +1484,7 @@ uint32 CStream::GetLengthMS() bool CStream::FillBuffer(ALuint *alBuffer) { +#ifndef MULTITHREADED_AUDIO if ( !HasSource() ) return false; if ( !IsOpened() ) @@ -1194,13 +1493,14 @@ bool CStream::FillBuffer(ALuint *alBuffer) return false; if ( !(alBuffer[1] != AL_NONE && alIsBuffer(alBuffer[1])) ) return false; - +#endif + uint32 size = m_pSoundFile->Decode(m_pBuffer); if( size == 0 ) return false; - - uint32 channelSize = size / m_pSoundFile->GetChannels(); + uint32 channelSize = size / m_pSoundFile->GetChannels(); + alBufferData(alBuffer[0], AL_FORMAT_MONO16, m_pBuffer, channelSize, m_pSoundFile->GetSampleRate()); // TODO: use just one buffer if we play mono if (m_pSoundFile->GetChannels() == 1) @@ -1210,6 +1510,26 @@ bool CStream::FillBuffer(ALuint *alBuffer) return true; } +#ifdef MULTITHREADED_AUDIO +bool CStream::QueueBuffers() +{ + bool buffersQueued = false; + std::pair buffers; + while (m_queueBuffers.peekPop(&buffers)) // beware: m_queueBuffers is tsQueue + { + ALuint leftBuf = buffers.first; + ALuint rightBuf = buffers.second; + + alSourceQueueBuffers(m_pAlSources[0], 1, &leftBuf); + alSourceQueueBuffers(m_pAlSources[1], 1, &rightBuf); + + buffersQueued = true; + } + return buffersQueued; +} +#endif + +// Only used in single-threaded audio or cutscene audio int32 CStream::FillBuffers() { int32 i = 0; @@ -1239,17 +1559,33 @@ void CStream::ClearBuffers() alSourceUnqueueBuffers(m_pAlSources[1], 1, &value); } -bool CStream::Setup(bool imSureQueueIsEmpty) +bool CStream::Setup(bool imSureQueueIsEmpty, bool lock) { if ( IsOpened() ) { - alSourcei(m_pAlSources[0], AL_LOOPING, AL_FALSE); - alSourcei(m_pAlSources[1], AL_LOOPING, AL_FALSE); +#ifdef MULTITHREADED_AUDIO + if (lock) + m_mutex.lock(); +#endif + if (!imSureQueueIsEmpty) { - SetPlay(false); + Stop(); ClearBuffers(); } +#ifdef MULTITHREADED_AUDIO + if (MusicManager.m_nMusicMode == MUSICMODE_CUTSCENE) { + m_pSoundFile->Seek(0); + } else { + m_bDoSeek = true; + m_SeekPos = 0; + } + + if (lock) + m_mutex.unlock(); +#else m_pSoundFile->Seek(0); +#endif + //SetPosition(0.0f, 0.0f, 0.0f); SetPitch(1.0f); //SetPan(m_nPan); @@ -1302,8 +1638,12 @@ void CStream::SetPlay(bool state) void CStream::Start() { if ( !HasSource() ) return; - if ( FillBuffers() != 0 ) - SetPlay(true); + +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + tsQueue>().swapNts(m_queueBuffers); // TSness not required, second thread always access it when stream mutex acquired +#endif + BuffersShouldBeFilled(); } void CStream::Stop() @@ -1325,9 +1665,23 @@ void CStream::Update() if ( !m_bPaused ) { - ALint totalBuffers[2] = { 0, 0 }; - ALint buffersProcessed[2] = { 0, 0 }; + bool buffersQueuedAndStarted = false; + bool buffersQueuedButNotStarted = false; +#ifdef MULTITHREADED_AUDIO + // Put it in here because we need totalBuffers after queueing to decide when to loop audio + if (m_bActive) + { + buffersQueuedAndStarted = QueueBuffers(); + if(buffersQueuedAndStarted) { + SetPlay(true); + } + } +#endif + + ALint totalBuffers[2] = {0, 0}; + ALint buffersProcessed[2] = {0, 0}; + // Relying a lot on left buffer states in here do @@ -1339,44 +1693,66 @@ void CStream::Update() alGetSourcei(m_pAlSources[1], AL_BUFFERS_QUEUED, &totalBuffers[1]); alGetSourcei(m_pAlSources[1], AL_BUFFERS_PROCESSED, &buffersProcessed[1]); } while (buffersProcessed[0] != buffersProcessed[1]); - + assert(buffersProcessed[0] == buffersProcessed[1]); // Correcting OpenAL concepts here: // AL_BUFFERS_QUEUED = Number of *all* buffers in queue, including processed, processing and pending // AL_BUFFERS_PROCESSED = Index of the buffer being processing right now. Buffers coming after that(have greater index) are pending buffers. // which means: totalBuffers[0] - buffersProcessed[0] = pending buffers - - bool buffersRefilled = false; - + // We should wait queue to be cleared to loop track, because position calculation relies on queue. if (m_nLoopCount != 1 && m_bActive && totalBuffers[0] == 0) { - Setup(true); - buffersRefilled = FillBuffers() != 0; - if (m_nLoopCount != 0) - m_nLoopCount--; +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + if (m_fillBuffers.empty() && m_queueBuffers.emptyNts()) // we already acquired stream mutex, which is enough for second thread. thus Nts variant +#endif + { + Setup(true, false); + BuffersShouldBeFilled(); // will also call SetPlay(true) + if (m_nLoopCount != 0) + m_nLoopCount--; + } } else { - while( buffersProcessed[0]-- ) + static std::queue> tempFillBuffer; + + while ( buffersProcessed[0]-- ) { ALuint buffer[2]; alSourceUnqueueBuffers(m_pAlSources[0], 1, &buffer[0]); alSourceUnqueueBuffers(m_pAlSources[1], 1, &buffer[1]); - - if (m_bActive && FillBuffer(buffer)) + + if (m_bActive) { - buffersRefilled = true; - alSourceQueueBuffers(m_pAlSources[0], 1, &buffer[0]); - alSourceQueueBuffers(m_pAlSources[1], 1, &buffer[1]); + tempFillBuffer.push(std::pair(buffer[0], buffer[1])); } } + + if (m_bActive && buffersProcessed[1]) + { +#ifdef MULTITHREADED_AUDIO + m_mutex.lock(); +#endif + while (!tempFillBuffer.empty()) { + auto elem = tempFillBuffer.front(); + tempFillBuffer.pop(); + buffersQueuedButNotStarted = BufferShouldBeFilledAndQueued(&elem); + } +#ifdef MULTITHREADED_AUDIO + m_mutex.unlock(); + FlagAsToBeProcessed(); +#endif + + } } - // Two reasons: 1-Source may be starved to audio and stopped itself, 2- We're already waiting it to starve and die for looping track! - if (m_bActive && (buffersRefilled || (totalBuffers[1] - buffersProcessed[1] != 0))) + // Source may be starved to audio and stopped itself + if (m_bActive && !buffersQueuedAndStarted && (buffersQueuedButNotStarted || (totalBuffers[1] - buffersProcessed[1] != 0))) SetPlay(true); } } @@ -1385,28 +1761,45 @@ void CStream::ProviderInit() { if ( m_bReset ) { - if ( Setup(true) ) + if ( Setup(true, false) ) // lock not needed, thread can't process streams with m_bReset set { SetPan(m_nPan); SetVolume(m_nVolume); SetLoopCount(m_nLoopCount); SetPosMS(m_nPosBeforeReset); - if (m_bActive) - FillBuffers(); - SetPlay(m_bActive); - if ( m_bPaused ) +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); +#endif + if(m_bActive) + BuffersShouldBeFilled(); + + if (m_bPaused) Pause(); + + m_bReset = false; + + } else { +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); +#endif + m_bReset = false; } - - m_bReset = false; } } void CStream::ProviderTerm() { +#ifdef MULTITHREADED_AUDIO + std::lock_guard lock(m_mutex); + + // unlike Close() we will reuse this stream, so clearing queues are important. + std::queue>().swap(m_fillBuffers); + tsQueue>().swapNts(m_queueBuffers); // stream mutex is already acquired, thus Nts variant +#endif m_bReset = true; m_nPosBeforeReset = GetPosMS(); - + + Stop(); ClearBuffers(); } diff --git a/src/audio/oal/stream.h b/src/audio/oal/stream.h index 9a2a2fbe..bdbf19e0 100644 --- a/src/audio/oal/stream.h +++ b/src/audio/oal/stream.h @@ -11,6 +11,7 @@ public: virtual ~IDecoder() { } virtual bool IsOpened() = 0; + virtual void FileOpen() = 0; virtual uint32 GetSampleSize() = 0; virtual uint32 GetSampleCount() = 0; @@ -48,12 +49,70 @@ public: uint32 GetLength() { + FileOpen(); // abort deferred init, we need length now - game has to cache audio file sizes return float(GetSampleCount()) * 1000.0f / float(GetSampleRate()); } virtual uint32 Decode(void *buffer) = 0; }; +#ifdef MULTITHREADED_AUDIO +template class tsQueue +{ +public: + tsQueue() : count(0) { } + + void push(const T &value) + { + std::lock_guard lock(m_mutex); + m_queue.push(value); + count++; + } + bool peekPop(T *retVal) + { + std::lock_guard lock(m_mutex); + if (count == 0) + return false; + + *retVal = m_queue.front(); + m_queue.pop(); + count--; + return true; + } + + void swapNts(tsQueue &replaceWith) + { + m_queue.swap(replaceWith.m_queue); + replaceWith.count = count; + } + + /* + void swapTs(tsQueue &replaceWith) + { + std::lock_guard lock(m_mutex); + std::lock_guard lock2(replaceWith.m_mutex); + swapNts(replaceWith); + } + */ + + bool emptyNts() + { + return count == 0; + } + + /* + bool emptyTs() + { + std::lock_guard lock(m_mutex); + return emptyNts(); + } + */ + + std::queue m_queue; + int count; + mutable std::mutex m_mutex; +}; +#endif class CStream { char m_aFilename[128]; @@ -63,6 +122,16 @@ class CStream bool m_bPaused; bool m_bActive; +public: +#ifdef MULTITHREADED_AUDIO + std::mutex m_mutex; + std::queue> m_fillBuffers; // left and right buffer + tsQueue> m_queueBuffers; + bool m_bDoSeek; + uint32 m_SeekPos; + uint8 m_nDeleteMe; // 1: add to delete list 2: already on delete list +#endif + void *m_pBuffer; bool m_bReset; @@ -72,7 +141,14 @@ class CStream int32 m_nLoopCount; IDecoder *m_pSoundFile; - + + void BuffersShouldBeFilled(); // all + bool BufferShouldBeFilledAndQueued(std::pair*); // two (left-right) +#ifdef MULTITHREADED_AUDIO + void FlagAsToBeProcessed(bool close = false); + bool QueueBuffers(); +#endif + bool HasSource(); void SetPosition(int i, float x, float y, float z); void SetPitch(float pitch); @@ -81,15 +157,15 @@ class CStream void SetPlay(bool state); bool FillBuffer(ALuint *alBuffer); - int32 FillBuffers(); + int32 FillBuffers(); void ClearBuffers(); -public: +//public: static void Initialise(); static void Terminate(); CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate = 32000); ~CStream(); - void Delete(); + void Close(); bool IsOpened(); bool IsPlaying(); @@ -100,12 +176,11 @@ public: uint32 GetPosMS(); uint32 GetLengthMS(); - bool Setup(bool imSureQueueIsEmpty = false); + bool Setup(bool imSureQueueIsEmpty = false, bool lock = true); void Start(); void Stop(); void Update(void); void SetLoopCount(int32); - void ProviderInit(); void ProviderTerm(); diff --git a/src/audio/sampman_oal.cpp b/src/audio/sampman_oal.cpp index 440e0927..cd2fa50a 100644 --- a/src/audio/sampman_oal.cpp +++ b/src/audio/sampman_oal.cpp @@ -34,6 +34,12 @@ #include "oal/oal_utils.h" #include "oal/aldlist.h" #include "oal/channel.h" + +#include +#ifdef MULTITHREADED_AUDIO +#include +#include +#endif #include "oal/stream.h" #include "AudioManager.h" @@ -521,7 +527,7 @@ _FindMP3s(void) if (aStream[0] && aStream[0]->IsOpened()) { total_ms = aStream[0]->GetLengthMS(); - delete aStream[0]; + aStream[0]->Close(); aStream[0] = NULL; OutputDebugString(fd.cFileName); @@ -595,7 +601,7 @@ _FindMP3s(void) if (aStream[0] && aStream[0]->IsOpened()) { total_ms = aStream[0]->GetLengthMS(); - delete aStream[0]; + aStream[0]->Close(); aStream[0] = NULL; OutputDebugString(fd.cFileName); @@ -655,7 +661,7 @@ _FindMP3s(void) if (aStream[0] && aStream[0]->IsOpened()) { total_ms = aStream[0]->GetLengthMS(); - delete aStream[0]; + aStream[0]->Close(); aStream[0] = NULL; OutputDebugString(fd.cFileName); @@ -811,6 +817,7 @@ cSampleManager::Initialise(void) return TRUE; EFXInit(); + CStream::Initialise(); { @@ -971,7 +978,7 @@ cSampleManager::Initialise(void) if ( aStream[0] && aStream[0]->IsOpened() ) { uint32 tatalms = aStream[0]->GetLengthMS(); - delete aStream[0]; + aStream[0]->Close(); aStream[0] = NULL; nStreamLength[i] = tatalms; @@ -1021,7 +1028,7 @@ cSampleManager::Initialise(void) nStreamPan[i] = 63; } } - + { _bSampmanInitialised = TRUE; @@ -1107,7 +1114,7 @@ cSampleManager::Terminate(void) CStream *stream = aStream[i]; if (stream) { - delete stream; + stream->Close(); aStream[i] = NULL; } } @@ -1688,7 +1695,7 @@ cSampleManager::PreloadStreamedFile(uint32 nFile, uint8 nStream) { if ( aStream[nStream] ) { - delete aStream[nStream]; + aStream[nStream]->Close(); aStream[nStream] = NULL; } @@ -1700,7 +1707,7 @@ cSampleManager::PreloadStreamedFile(uint32 nFile, uint8 nStream) aStream[nStream] = stream; if ( !stream->Setup() ) { - delete stream; + stream->Close(); aStream[nStream] = NULL; } } @@ -1747,7 +1754,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) if ( aStream[nStream] ) { - delete aStream[nStream]; + aStream[nStream]->Close(); aStream[nStream] = NULL; } if ( nFile == STREAMED_SOUND_RADIO_MP3_PLAYER ) @@ -1780,7 +1787,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { - delete stream; + stream->Close(); aStream[nStream] = NULL; } return FALSE; @@ -1805,7 +1812,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) _bIsMp3Active = TRUE; return TRUE; } else { - delete aStream[nStream]; + aStream[nStream]->Close(); aStream[nStream] = NULL; } // fall through, start playing from another song @@ -1839,7 +1846,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { - delete stream; + stream->Close(); aStream[nStream] = NULL; } return FALSE; @@ -1861,7 +1868,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) #endif return TRUE; } else { - delete aStream[nStream]; + aStream[nStream]->Close(); aStream[nStream] = NULL; } @@ -1888,7 +1895,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { - delete stream; + stream->Close(); aStream[nStream] = NULL; } return FALSE; @@ -1903,7 +1910,7 @@ cSampleManager::StopStreamedFile(uint8 nStream) if ( stream ) { - delete stream; + stream->Close(); aStream[nStream] = NULL; if ( nStream == 0 ) diff --git a/src/core/config.h b/src/core/config.h index 006ebad5..f19da754 100644 --- a/src/core/config.h +++ b/src/core/config.h @@ -398,6 +398,7 @@ static_assert(false, "SUPPORT_XBOX_SCRIPT and SUPPORT_MOBILE_SCRIPT are mutually //#define PS2_AUDIO_PATHS // changes audio paths for cutscenes and radio to PS2 paths (needs vbdec on MSS builds) //#define AUDIO_OAL_USE_SNDFILE // use libsndfile to decode WAVs instead of our internal decoder #define AUDIO_OAL_USE_MPG123 // use mpg123 to support mp3 files +#define MULTITHREADED_AUDIO #ifdef AUDIO_OPUS #define AUDIO_OAL_USE_OPUS // enable support of opus files