From 5458632c405fd81e76e625ba9dfabe8831509d1b Mon Sep 17 00:00:00 2001 From: erorcun Date: Sat, 26 Jun 2021 23:59:40 +0300 Subject: [PATCH] Multi-threaded audio fixes --- src/audio/oal/stream.cpp | 93 ++++++++++++++++----------- src/audio/oal/stream.h | 7 ++- src/audio/sampman_oal.cpp | 129 ++++++++++++++------------------------ src/core/config.h | 3 +- 4 files changed, 113 insertions(+), 119 deletions(-) diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp index 68847906..8b627e2a 100644 --- a/src/audio/oal/stream.cpp +++ b/src/audio/oal/stream.cpp @@ -1018,42 +1018,23 @@ CStream::FlagAsToBeProcessed(bool close) 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; }); + gAudioThreadCv.wait(queueMutex, [] { return gStreamsToProcess.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; - } + } else continue; - } } std::unique_lock lock(stream->m_mutex); @@ -1062,17 +1043,22 @@ void audioFileOpsThread() bool insertBufsAfterCheck = false; do { - if (stream->m_nDeleteMe == 1) { - m_streamsToDelete.push(stream); - stream->m_nDeleteMe = 2; - break; - } else if (stream->m_nDeleteMe == 2) { + if (!stream->IsOpened()) { + // We MUST do that here, because we release mutex for m_pSoundFile->Seek() and m_pSoundFile->Decode() since they're costly + if (stream->m_pSoundFile) { + delete stream->m_pSoundFile; + stream->m_pSoundFile = nil; + } + + if (stream->m_pBuffer) { + free(stream->m_pBuffer); + stream->m_pBuffer = nil; + } + lock.unlock(); + stream->m_closeCv.notify_one(); break; } - if (!stream->IsOpened()) - break; - if (stream->m_bReset) break; @@ -1152,14 +1138,14 @@ void CStream::Terminate() #endif } -CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate) : +CStream::CStream(ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]) : m_pAlSources(sources), m_alBuffers(buffers), m_pBuffer(nil), m_bPaused(false), m_bActive(false), #ifdef MULTITHREADED_AUDIO - m_nDeleteMe(false), + m_bIExist(false), m_bDoSeek(false), m_SeekPos(0), #endif @@ -1171,6 +1157,31 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU m_nLoopCount(1) { +} + +bool CStream::Open(const char* filename, uint32 overrideSampleRate) +{ + if (IsOpened()) return false; + +#ifdef MULTITHREADED_AUDIO + std::unique_lock lock(m_mutex); + + CStream *stream = this; + // Wait for thread to close old one. We can't close it here, because the thread might be running Decode() or Seek(), while mutex is released + m_closeCv.wait(lock, [this] { return m_pSoundFile == nil && m_pBuffer == nil; }); + + m_bDoSeek = false; + m_SeekPos = 0; +#endif + + m_bPaused = false; + m_bActive = false; + m_bReset = false; + m_nVolume = 0; + m_nPan = 0; + m_nPosBeforeReset = 0; + m_nLoopCount = 1; + // Be case-insensitive on linux (from https://github.com/OneSadCookie/fcaseopen/) #if !defined(_WIN32) char *real = casepath(filename); @@ -1205,7 +1216,7 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU else m_pSoundFile = nil; - if ( IsOpened() ) + if ( m_pSoundFile && m_pSoundFile->IsOpened() ) { uint32 bufSize = m_pSoundFile->GetBufferSize(); if(bufSize != 0) { // Otherwise it's deferred @@ -1220,8 +1231,12 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU 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; +#ifdef MULTITHREADED_AUDIO + m_bIExist = true; +#endif + return true; } + return false; } CStream::~CStream() @@ -1231,18 +1246,21 @@ CStream::~CStream() void CStream::Close() { + if(!IsOpened()) return; + #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 + m_bIExist = false; + // clearing buffer queues are not needed. after m_bIExist is cleared, this stream is ded } FlagAsToBeProcessed(true); #else + Stop(); ClearBuffers(); @@ -1265,9 +1283,14 @@ bool CStream::HasSource() return (m_pAlSources[0] != AL_NONE) && (m_pAlSources[1] != AL_NONE); } +// m_bIExist only written in main thread, thus mutex is not needed on main thread bool CStream::IsOpened() { +#ifdef MULTITHREADED_AUDIO + return m_bIExist; +#else return m_pSoundFile && m_pSoundFile->IsOpened(); +#endif } bool CStream::IsPlaying() diff --git a/src/audio/oal/stream.h b/src/audio/oal/stream.h index bdbf19e0..10b595c1 100644 --- a/src/audio/oal/stream.h +++ b/src/audio/oal/stream.h @@ -127,9 +127,10 @@ public: std::mutex m_mutex; std::queue> m_fillBuffers; // left and right buffer tsQueue> m_queueBuffers; + std::condition_variable m_closeCv; bool m_bDoSeek; uint32 m_SeekPos; - uint8 m_nDeleteMe; // 1: add to delete list 2: already on delete list + bool m_bIExist; #endif void *m_pBuffer; @@ -163,8 +164,10 @@ public: static void Initialise(); static void Terminate(); - CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS], uint32 overrideSampleRate = 32000); + CStream(ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]); ~CStream(); + void Delete(); + bool Open(const char *filename, uint32 overrideSampleRate = 32000); void Close(); bool IsOpened(); diff --git a/src/audio/sampman_oal.cpp b/src/audio/sampman_oal.cpp index d546acf2..6c25cf79 100644 --- a/src/audio/sampman_oal.cpp +++ b/src/audio/sampman_oal.cpp @@ -39,6 +39,7 @@ #ifdef MULTITHREADED_AUDIO #include #include +#include #endif #include "oal/stream.h" @@ -521,14 +522,10 @@ _FindMP3s(void) continue; } } - aStream[0] = new CStream(filepath, ALStreamSources[0], ALStreamBuffers[0]); - - if (aStream[0] && aStream[0]->IsOpened()) + if (aStream[0] && aStream[0]->Open(filepath)) { total_ms = aStream[0]->GetLengthMS(); aStream[0]->Close(); - aStream[0] = NULL; - OutputDebugString(fd.cFileName); _pMP3List = new tMP3Entry; @@ -579,13 +576,10 @@ _FindMP3s(void) else bShortcut = FALSE; - aStream[0] = new CStream(filepath, ALStreamSources[0], ALStreamBuffers[0]); - - if (aStream[0] && aStream[0]->IsOpened()) + if (aStream[0] && aStream[0]->Open(filepath)) { total_ms = aStream[0]->GetLengthMS(); aStream[0]->Close(); - aStream[0] = NULL; pList->pNext = new tMP3Entry; @@ -739,6 +733,9 @@ cSampleManager::Initialise(void) EFXInit(); + for(int i = 0; i < MAX_STREAMS; i++) + aStream[i] = new CStream(ALStreamSources[i], ALStreamBuffers[i]); + CStream::Initialise(); { @@ -892,14 +889,12 @@ cSampleManager::Initialise(void) debug("Cannot load audio cache\n"); #endif - for(int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++) { - aStream[0] = new CStream(StreamedNameTable[i], ALStreamSources[0], ALStreamBuffers[0], IsThisTrackAt16KHz(i) ? 16000 : 32000); - - if(aStream[0] && aStream[0]->IsOpened()) { + for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) + { + if ( aStream[0] && aStream[0]->Open(StreamedNameTable[i], IsThisTrackAt16KHz(i) ? 16000 : 32000) ) + { uint32 tatalms = aStream[0]->GetLengthMS(); aStream[0]->Close(); - aStream[0] = NULL; - nStreamLength[i] = tatalms; } else USERERROR("Can't open '%s'\n", StreamedNameTable[i]); @@ -941,7 +936,8 @@ cSampleManager::Initialise(void) { for ( int32 i = 0; i < MAX_STREAMS; i++ ) { - aStream[i] = NULL; + aStream[i]->Close(); + nStreamVolume[i] = 100; nStreamPan[i] = 63; } @@ -1028,15 +1024,8 @@ void cSampleManager::Terminate(void) { for (int32 i = 0; i < MAX_STREAMS; i++) - { - CStream *stream = aStream[i]; - if (stream) - { - stream->Close(); - aStream[i] = NULL; - } - } - + aStream[i]->Close(); + for ( int32 i = 0; i < NUM_CHANNELS; i++ ) aChannel[i].Term(); @@ -1086,6 +1075,9 @@ cSampleManager::Terminate(void) CStream::Terminate(); + for(int32 i = 0; i < MAX_STREAMS; i++) + delete aStream[i]; + if ( nSampleBankMemoryStartAddress[SFX_BANK_0] != 0 ) { free((void *)nSampleBankMemoryStartAddress[SFX_BANK_0]); @@ -1612,22 +1604,16 @@ cSampleManager::PreloadStreamedFile(uint8 nFile, uint8 nStream) if ( nFile < TOTAL_STREAMED_SOUNDS ) { - if ( aStream[nStream] ) - { - aStream[nStream]->Close(); - aStream[nStream] = NULL; - } - + CStream *stream = aStream[nStream]; + + stream->Close(); + strcpy(filename, StreamedNameTable[nFile]); - CStream *stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - ASSERT(stream != NULL); - - aStream[nStream] = stream; + stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); if ( !stream->Setup() ) { stream->Close(); - aStream[nStream] = NULL; } } } @@ -1639,7 +1625,7 @@ cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { stream->SetPause(nPauseFlag != FALSE); } @@ -1652,12 +1638,9 @@ cSampleManager::StartPreloadedStreamedFile(uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { - if ( stream->IsOpened() ) - { - stream->Start(); - } + stream->Start(); } } @@ -1671,11 +1654,8 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) if ( nFile >= TOTAL_STREAMED_SOUNDS ) return FALSE; - if ( aStream[nStream] ) - { - aStream[nStream]->Close(); - aStream[nStream] = NULL; - } + aStream[nStream]->Close(); + if ( nFile == STREAMED_SOUND_RADIO_MP3_PLAYER ) { do @@ -1691,12 +1671,10 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) if(!_GetMP3PosFromStreamPos(&position, &e) && !e) { nFile = 0; strcpy(filename, StreamedNameTable[nFile]); - - CStream* stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - aStream[nStream] = stream; - - if (stream->Setup()) { + CStream *stream = aStream[nStream]; + stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + if ( stream->Setup() ) { if (position != 0) stream->SetPosMS(position); @@ -1705,18 +1683,17 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { stream->Close(); - aStream[nStream] = NULL; } return FALSE; } else { - if ( e->pLinkPath != NULL ) - aStream[nStream] = new CStream(e->pLinkPath, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + if (e->pLinkPath != NULL) + aStream[nStream]->Open(e->pLinkPath, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); else { strcpy(filename, _mp3DirectoryPath); strcat(filename, e->aFilename); - - aStream[nStream] = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream]); + + aStream[nStream]->Open(filename); } if (aStream[nStream]->Setup()) { @@ -1729,7 +1706,6 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { aStream[nStream]->Close(); - aStream[nStream] = NULL; } // fall through, start playing from another song } @@ -1748,9 +1724,8 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) _bIsMp3Active = 0; strcpy(filename, StreamedNameTable[nFile]); - CStream* stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - - aStream[nStream] = stream; + CStream* stream = aStream[nStream]; + stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); if (stream->Setup()) { if (position != 0) @@ -1761,18 +1736,16 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { stream->Close(); - aStream[nStream] = NULL; } return FALSE; } } - if(mp3->pLinkPath != NULL) - aStream[nStream] = new CStream(mp3->pLinkPath, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + if (mp3->pLinkPath != NULL) + aStream[nStream]->Open(mp3->pLinkPath, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); else { strcpy(filename, _mp3DirectoryPath); strcat(filename, mp3->aFilename); - - aStream[nStream] = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream]); + aStream[nStream]->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); } if (aStream[nStream]->Setup()) { @@ -1783,7 +1756,6 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { aStream[nStream]->Close(); - aStream[nStream] = NULL; } } @@ -1795,9 +1767,9 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) } strcpy(filename, StreamedNameTable[nFile]); - CStream *stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + CStream *stream = aStream[nStream]; - aStream[nStream] = stream; + aStream[nStream]->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); if ( stream->Setup() ) { if (position != 0) @@ -1808,7 +1780,6 @@ cSampleManager::StartStreamedFile(uint8 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { stream->Close(); - aStream[nStream] = NULL; } return FALSE; } @@ -1820,14 +1791,10 @@ cSampleManager::StopStreamedFile(uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) - { - stream->Close(); - aStream[nStream] = NULL; + stream->Close(); - if ( nStream == 0 ) - _bIsMp3Active = FALSE; - } + if ( nStream == 0 ) + _bIsMp3Active = FALSE; } int32 @@ -1837,7 +1804,7 @@ cSampleManager::GetStreamedFilePosition(uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { if ( _bIsMp3Active ) { @@ -1875,7 +1842,7 @@ cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, uint8 nEffect CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { if ( nEffectFlag ) stream->SetVolume(m_nEffectsFadeVolume*nVolume*m_nEffectsVolume >> 14); @@ -1901,7 +1868,7 @@ cSampleManager::IsStreamPlaying(uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { if ( stream->IsPlaying() ) return TRUE; @@ -1917,7 +1884,7 @@ cSampleManager::Service(void) { CStream *stream = aStream[i]; - if ( stream ) + if ( stream->IsOpened() ) stream->Update(); } int refCount = CChannel::channelsThatNeedService; diff --git a/src/core/config.h b/src/core/config.h index 8f10e10d..1c7b06d3 100644 --- a/src/core/config.h +++ b/src/core/config.h @@ -399,8 +399,8 @@ enum Config { //#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 #define PAUSE_RADIO_IN_FRONTEND // pause radio when game is paused +#define MULTITHREADED_AUDIO // for streams. requires C++11 or later #ifdef AUDIO_OPUS #define AUDIO_OAL_USE_OPUS // enable support of opus files @@ -527,6 +527,7 @@ enum Config { #undef CANCELLABLE_CAR_ENTER #undef IMPROVED_CAMERA #undef FREE_CAM +#undef MULTITHREADED_AUDIO #undef RADIO_SCROLL_TO_PREV_STATION #undef BIG_IMG #undef PS2_AUDIO_CHANNELS