diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/audio/oal/stream.cpp | 612 | ||||
-rw-r--r-- | src/audio/oal/stream.h | 90 | ||||
-rw-r--r-- | src/audio/sampman_oal.cpp | 217 | ||||
-rw-r--r-- | src/core/config.h | 2 |
4 files changed, 676 insertions, 245 deletions
diff --git a/src/audio/oal/stream.cpp b/src/audio/oal/stream.cpp index ed73e940..13fbcd34 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 <opusfile.h> #endif +#include <queue> +#include <utility> + +#ifdef MULTITHREADED_AUDIO +#include <iostream> +#include <thread> +#include <mutex> +#include <condition_variable> +#include "MusicManager.h" +#include "stream.h" + +std::thread gAudioThread; +std::mutex gAudioThreadQueueMutex; +std::condition_variable gAudioThreadCv; +bool gAudioThreadTerm = false; +std::queue<CStream*> 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<std::mutex> 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,28 +649,41 @@ 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; + void FileOpen() + { + if(!m_bFileNotOpenedYet) return; - if (IsOpened()) - { - mpg123_format_none(m_pMH); - mpg123_format(m_pMH, rate, channels, encoding); - } + 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; } }; @@ -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,169 @@ 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<std::pair<ALuint, ALuint>> tempQueue; + for(int i = 0; i < NUM_STREAMBUFFERS / 2; i++) { + tempQueue.push(std::pair<ALuint, ALuint>(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<std::pair<ALuint, ALuint>>().swap(m_fillBuffers); +#endif + if ( FillBuffers() != 0 ) + { + SetPlay(true); + } +} + +// returns whether it's queued (not on multi-thread) +bool +CStream::BufferShouldBeFilledAndQueued(std::pair<ALuint, ALuint>* 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(); +} + +void audioFileOpsThread() +{ + do + { + CStream *stream; + { + // Just a semaphore + std::unique_lock<std::mutex> queueMutex(gAudioThreadQueueMutex); + gAudioThreadCv.wait(queueMutex, [] { return gStreamsToProcess.size() > 0 || gAudioThreadTerm; }); + if (gAudioThreadTerm) + return; + + if (!gStreamsToProcess.empty()) { + stream = gStreamsToProcess.front(); + gStreamsToProcess.pop(); + } else + continue; + } + + std::unique_lock<std::mutex> lock(stream->m_mutex); + + std::pair<ALuint, ALuint> buffers, *lastBufAddr; + bool insertBufsAfterCheck = false; + + do { + 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->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,14 +1199,27 @@ 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) : +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_bIExist(false), + m_bDoSeek(false), + m_SeekPos(0), +#endif m_pSoundFile(nil), m_bReset(false), m_nVolume(0), @@ -989,6 +1228,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<std::mutex> 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); @@ -1025,44 +1289,66 @@ CStream::CStream(char *filename, ALuint *sources, ALuint (&buffers)[NUM_STREAMBU else m_pSoundFile = nil; - if ( IsOpened() ) + if ( m_pSoundFile && m_pSoundFile->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); - - return; + 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); + } +#ifdef MULTITHREADED_AUDIO + m_bIExist = true; +#endif + return true; } + return false; } CStream::~CStream() { - Delete(); + assert(!IsOpened()); } -void CStream::Delete() +void CStream::Close() { + if(!IsOpened()) return; + +#ifdef MULTITHREADED_AUDIO + { + std::lock_guard<std::mutex> lock(m_mutex); + + Stop(); + ClearBuffers(); + m_bIExist = false; + // clearing buffer queues are not needed. after m_bIExist is cleared, 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() @@ -1070,9 +1356,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() @@ -1086,6 +1377,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<std::mutex> 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 +1459,24 @@ void CStream::SetPan(uint8 nPan) void CStream::SetPosMS(uint32 nPos) { if ( !IsOpened() ) return; - m_pSoundFile->Seek(nPos); + +#ifdef MULTITHREADED_AUDIO + std::lock_guard<std::mutex> lock(m_mutex); + + std::queue<std::pair<ALuint, ALuint>>().swap(m_fillBuffers); + tsQueue<std::pair<ALuint, ALuint>>().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 +1484,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<std::mutex> 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 +1507,7 @@ uint32 CStream::GetLengthMS() bool CStream::FillBuffer(ALuint *alBuffer) { +#ifndef MULTITHREADED_AUDIO if ( !HasSource() ) return false; if ( !IsOpened() ) @@ -1194,13 +1516,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 +1533,26 @@ bool CStream::FillBuffer(ALuint *alBuffer) return true; } +#ifdef MULTITHREADED_AUDIO +bool CStream::QueueBuffers() +{ + bool buffersQueued = false; + std::pair<ALuint, ALuint> 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 +1582,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 +1661,12 @@ void CStream::SetPlay(bool state) void CStream::Start() { if ( !HasSource() ) return; - if ( FillBuffers() != 0 ) - SetPlay(true); + +#ifdef MULTITHREADED_AUDIO + std::lock_guard<std::mutex> lock(m_mutex); + tsQueue<std::pair<ALuint, ALuint>>().swapNts(m_queueBuffers); // TSness not required, second thread always access it when stream mutex acquired +#endif + BuffersShouldBeFilled(); } void CStream::Stop() @@ -1325,9 +1688,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 +1716,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<std::mutex> 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<std::pair<ALuint, ALuint>> 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<ALuint, ALuint>(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 +1784,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<std::mutex> lock(m_mutex); +#endif + if(m_bActive) + BuffersShouldBeFilled(); + + if (m_bPaused) Pause(); + + m_bReset = false; + + } else { +#ifdef MULTITHREADED_AUDIO + std::unique_lock<std::mutex> lock(m_mutex); +#endif + m_bReset = false; } - - m_bReset = false; } } void CStream::ProviderTerm() { +#ifdef MULTITHREADED_AUDIO + std::lock_guard<std::mutex> lock(m_mutex); + + // unlike Close() we will reuse this stream, so clearing queues are important. + std::queue<std::pair<ALuint, ALuint>>().swap(m_fillBuffers); + tsQueue<std::pair<ALuint, ALuint>>().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..10b595c1 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 <typename T> class tsQueue +{ +public: + tsQueue() : count(0) { } + + void push(const T &value) + { + std::lock_guard<std::mutex> lock(m_mutex); + m_queue.push(value); + count++; + } + + bool peekPop(T *retVal) + { + std::lock_guard<std::mutex> lock(m_mutex); + if (count == 0) + return false; + + *retVal = m_queue.front(); + m_queue.pop(); + count--; + return true; + } + + void swapNts(tsQueue<T> &replaceWith) + { + m_queue.swap(replaceWith.m_queue); + replaceWith.count = count; + } + + /* + void swapTs(tsQueue<T> &replaceWith) + { + std::lock_guard<std::mutex> lock(m_mutex); + std::lock_guard<std::mutex> lock2(replaceWith.m_mutex); + swapNts(replaceWith); + } + */ + + bool emptyNts() + { + return count == 0; + } + /* + bool emptyTs() + { + std::lock_guard<std::mutex> lock(m_mutex); + return emptyNts(); + } + */ + + std::queue<T> m_queue; + int count; + mutable std::mutex m_mutex; +}; +#endif class CStream { char m_aFilename[128]; @@ -63,6 +122,17 @@ class CStream bool m_bPaused; bool m_bActive; +public: +#ifdef MULTITHREADED_AUDIO + std::mutex m_mutex; + std::queue<std::pair<ALuint, ALuint>> m_fillBuffers; // left and right buffer + tsQueue<std::pair<ALuint, ALuint>> m_queueBuffers; + std::condition_variable m_closeCv; + bool m_bDoSeek; + uint32 m_SeekPos; + bool m_bIExist; +#endif + void *m_pBuffer; bool m_bReset; @@ -72,7 +142,14 @@ class CStream int32 m_nLoopCount; IDecoder *m_pSoundFile; - + + void BuffersShouldBeFilled(); // all + bool BufferShouldBeFilledAndQueued(std::pair<ALuint, ALuint>*); // 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 +158,17 @@ 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(ALuint *sources, ALuint (&buffers)[NUM_STREAMBUFFERS]); ~CStream(); void Delete(); + bool Open(const char *filename, uint32 overrideSampleRate = 32000); + void Close(); bool IsOpened(); bool IsPlaying(); @@ -100,12 +179,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 186990a4..e95bc978 100644 --- a/src/audio/sampman_oal.cpp +++ b/src/audio/sampman_oal.cpp @@ -34,6 +34,13 @@ #include "oal/oal_utils.h" #include "oal/aldlist.h" #include "oal/channel.h" + +#include <utility> +#ifdef MULTITHREADED_AUDIO +#include <mutex> +#include <queue> +#include <condition_variable> +#endif #include "oal/stream.h" #include "AudioManager.h" @@ -590,13 +597,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(); - delete aStream[0]; - aStream[0] = NULL; + aStream[0]->Close(); OutputDebugString(fd.cFileName); @@ -664,13 +668,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(); - delete aStream[0]; - aStream[0] = NULL; + aStream[0]->Close(); OutputDebugString(fd.cFileName); @@ -724,13 +725,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(); - delete aStream[0]; - aStream[0] = NULL; + aStream[0]->Close(); OutputDebugString(fd.cFileName); @@ -885,6 +883,10 @@ cSampleManager::Initialise(void) return TRUE; SetUpDebugBanksInfo(); EFXInit(); + + for(int i = 0; i < MAX_STREAMS; i++) + aStream[i] = new CStream(ALStreamSources[i], ALStreamBuffers[i]); + CStream::Initialise(); { @@ -1040,31 +1042,24 @@ cSampleManager::Initialise(void) for ( int32 i = 0; i < TOTAL_STREAMED_SOUNDS; i++ ) { + bool opened = false; char filename[MAX_PATH]; sprintf(filename, "%s.VB", StreamedNameTable[i]); - aStream[0] = new CStream(filename, ALStreamSources[0], ALStreamBuffers[0], IsThisTrackAt16KHz(i) ? 16000 : 32000); - if ( aStream[0] && !aStream[0]->IsOpened() ) - { - delete aStream[0]; - aStream[0] = NULL; - } - - if ( !aStream[0] ) + if ( aStream[0] ) + opened = aStream[0]->Open(filename, IsThisTrackAt16KHz(i) ? 16000 : 32000) ) + + if ( !opened ) { sprintf(filename, "%s.MP3", StreamedNameTable[i]); - aStream[0] = new CStream(filename, ALStreamSources[0], ALStreamBuffers[0], IsThisTrackAt16KHz(i) ? 16000 : 32000); - if ( aStream[0] && !aStream[0]->IsOpened() ) + if ( aStream[0] ) { - delete aStream[0]; - aStream[0] = NULL; + opened = aStream[0]->Open(filename, IsThisTrackAt16KHz(i) ? 16000 : 32000) } } - - if ( aStream[0] && aStream[0]->IsOpened() ) + if ( opened ) { uint32 tatalms = aStream[0]->GetLengthMS(); - delete aStream[0]; - aStream[0] = NULL; + aStream[0]->Close(); nStreamLength[i] = tatalms; } @@ -1108,12 +1103,13 @@ cSampleManager::Initialise(void) { for ( int32 i = 0; i < MAX_STREAMS; i++ ) { - aStream[i] = NULL; + aStream[i]->Close(); + nStreamVolume[i] = 100; nStreamPan[i] = 63; } } - + { _bSampmanInitialised = TRUE; @@ -1195,14 +1191,7 @@ void cSampleManager::Terminate(void) { for (int32 i = 0; i < MAX_STREAMS; i++) - { - CStream *stream = aStream[i]; - if (stream) - { - delete stream; - aStream[i] = NULL; - } - } + aStream[i]->Close(); for ( int32 i = 0; i < NUM_CHANNELS; i++ ) aChannel[i].Term(); @@ -1253,6 +1242,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]); @@ -1778,40 +1770,20 @@ cSampleManager::PreloadStreamedFile(uint32 nFile, uint8 nStream) if ( nFile < TOTAL_STREAMED_SOUNDS ) { - if ( aStream[nStream] ) - { - delete aStream[nStream]; - aStream[nStream] = NULL; - } + CStream *stream = aStream[nStream]; + stream->Close(); sprintf(filename, "%s.VB", StreamedNameTable[nFile]); - CStream *stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - - if ( stream && !stream->IsOpened() ) - { - delete stream; - stream = NULL; - } - - if (!stream) + bool opened = stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + if ( !opened ) { sprintf(filename, "%s.MP3", StreamedNameTable[nFile]); - stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - if ( stream && !stream->IsOpened() ) - { - delete stream; - stream = NULL; - } + opened = stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); } - - - ASSERT(stream != NULL); - aStream[nStream] = stream; - if ( !stream->Setup() ) + if ( opened && !stream->Setup() ) { - delete stream; - aStream[nStream] = NULL; + stream->Close(); } } } @@ -1823,7 +1795,7 @@ cSampleManager::PauseStream(bool8 nPauseFlag, uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { stream->SetPause(nPauseFlag != FALSE); } @@ -1836,12 +1808,9 @@ cSampleManager::StartPreloadedStreamedFile(uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { - if ( stream->IsOpened() ) - { - stream->Start(); - } + stream->Start(); } } @@ -1855,11 +1824,8 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) if ( nFile >= TOTAL_STREAMED_SOUNDS ) return FALSE; - if ( aStream[nStream] ) - { - delete aStream[nStream]; - aStream[nStream] = NULL; - } + aStream[nStream]->Close(); + if ( nFile == STREAMED_SOUND_RADIO_MP3_PLAYER ) { do @@ -1876,27 +1842,17 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) nFile = 0; sprintf(filename, "%s.VB", StreamedNameTable[nFile]); - CStream* stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - - if ( stream && !stream->IsOpened() ) - { - delete stream; - stream = NULL; - } - if (!stream) + CStream *stream = aStream[nStream]; + bool opened = stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + + if ( !opened ) { sprintf(filename, "%s.MP3", StreamedNameTable[nFile]); - stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - if ( stream && !stream->IsOpened() ) - { - delete stream; - stream = NULL; - } + opened = stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); } - aStream[nStream] = stream; - - if (stream->Setup()) { + + if ( opened && stream->Setup() ) { stream->SetLoopCount(nStreamLoopedFlag[nStream] ? 0 : 1); nStreamLoopedFlag[nStream] = TRUE; if (position != 0) @@ -1906,20 +1862,19 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { - delete stream; - aStream[nStream] = NULL; + stream->Close(); } return FALSE; } else { if (e->pLinkPath != NULL) - aStream[nStream] = new CStream(e->pLinkPath, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + 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()) { @@ -1931,8 +1886,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) _bIsMp3Active = TRUE; return TRUE; } else { - delete aStream[nStream]; - aStream[nStream] = NULL; + aStream[nStream]->Close(); } // fall through, start playing from another song } @@ -1951,28 +1905,16 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) _bIsMp3Active = 0; sprintf(filename, "%s.VB", StreamedNameTable[nFile]); - CStream* stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - - if ( stream && !stream->IsOpened() ) - { - delete stream; - stream = NULL; - } + CStream* stream = aStream[nStream]; + bool opened = stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - if (!stream) + if ( !opened ) { sprintf(filename, "%s.MP3", StreamedNameTable[nFile]); - stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); - if ( stream && !stream->IsOpened() ) - { - delete stream; - stream = NULL; - } + opened = stream->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); } - - aStream[nStream] = stream; - - if (stream->Setup()) { + + if (opened && stream->Setup()) { stream->SetLoopCount(nStreamLoopedFlag[nStream] ? 0 : 1); nStreamLoopedFlag[nStream] = TRUE; if (position != 0) @@ -1982,19 +1924,18 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { - delete stream; - aStream[nStream] = NULL; + stream->Close(); } return FALSE; } } if (mp3->pLinkPath != NULL) - aStream[nStream] = new CStream(mp3->pLinkPath, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + 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], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + aStream[nStream]->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); } if (aStream[nStream]->Setup()) { @@ -2004,8 +1945,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) #endif return TRUE; } else { - delete aStream[nStream]; - aStream[nStream] = NULL; + aStream[nStream]->Close(); } } @@ -2017,7 +1957,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) } sprintf(filename, "%s.VB", StreamedNameTable[nFile]); - CStream *stream = new CStream(filename, ALStreamSources[nStream], ALStreamBuffers[nStream], IsThisTrackAt16KHz(nFile) ? 16000 : 32000); + CStream *stream = aStream[nStream]; if ( stream && !stream->IsOpened() ) { @@ -2036,7 +1976,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) } } - aStream[nStream] = stream; + aStream[nStream]->Open(filename, IsThisTrackAt16KHz(nFile) ? 16000 : 32000); if ( stream->Setup() ) { stream->SetLoopCount(nStreamLoopedFlag[nStream] ? 0 : 1); @@ -2048,8 +1988,7 @@ cSampleManager::StartStreamedFile(uint32 nFile, uint32 nPos, uint8 nStream) return TRUE; } else { - delete stream; - aStream[nStream] = NULL; + stream->Close(); } return FALSE; } @@ -2061,14 +2000,10 @@ cSampleManager::StopStreamedFile(uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) - { - delete stream; - aStream[nStream] = NULL; + stream->Close(); - if ( nStream == 0 ) - _bIsMp3Active = FALSE; - } + if ( nStream == 0 ) + _bIsMp3Active = FALSE; } int32 @@ -2078,7 +2013,7 @@ cSampleManager::GetStreamedFilePosition(uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { if ( _bIsMp3Active ) { @@ -2121,7 +2056,7 @@ cSampleManager::SetStreamedVolumeAndPan(uint8 nVolume, uint8 nPan, bool8 nEffect CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { if ( nEffectFlag ) { if ( nStream == 1 || nStream == 2 ) @@ -2151,7 +2086,7 @@ cSampleManager::IsStreamPlaying(uint8 nStream) CStream *stream = aStream[nStream]; - if ( stream ) + if ( stream->IsOpened() ) { if ( stream->IsPlaying() ) return TRUE; @@ -2167,7 +2102,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 f94384cf..bb1e4dbe 100644 --- a/src/core/config.h +++ b/src/core/config.h @@ -403,6 +403,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 // for streams. requires C++11 or later #ifdef AUDIO_OPUS #define AUDIO_OAL_USE_OPUS // enable support of opus files @@ -516,5 +517,6 @@ static_assert(false, "SUPPORT_XBOX_SCRIPT and SUPPORT_MOBILE_SCRIPT are mutually #undef FREE_CAM #undef BIG_IMG #undef PS2_AUDIO_CHANNELS +#undef MULTITHREADED_AUDIO #undef RADIO_SCROLL_TO_PREV_STATION #endif |