From 03c453564831fe34433065d05df08dcab122af56 Mon Sep 17 00:00:00 2001 From: Shyotl Date: Wed, 16 Oct 2013 21:32:08 -0500 Subject: [PATCH] Made further refinements to the audioengine: -Looping syncslaves are stopped when there is no syncmaster. -Hardened against edge cases regarding soundsource priority sorting. -Fixed issue with syncmasters being marked as having looped when instead they just started playing (caused double-start of syncslaves). -Updated some comments. --- indra/llaudio/llaudioengine.cpp | 130 ++++++++++++++++--------- indra/llaudio/llaudioengine.h | 1 + indra/llaudio/llaudioengine_fmod.cpp | 1 + indra/llaudio/llaudioengine_fmodex.cpp | 1 + indra/llaudio/llaudioengine_openal.cpp | 1 + 5 files changed, 86 insertions(+), 48 deletions(-) diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp index 1f7815b19..c1f362d32 100644 --- a/indra/llaudio/llaudioengine.cpp +++ b/indra/llaudio/llaudioengine.cpp @@ -284,13 +284,14 @@ void LLAudioEngine::updateChannels() checkStates(); } -typedef std::pair audio_source_t; struct SourcePriorityComparator { - bool operator() (const audio_source_t& lhs, const audio_source_t& rhs) const + bool operator() (const LLAudioSource* lhs, const LLAudioSource* rhs) const { - //Sort by priority value. If equal, sync masters get higher priority. - return lhs.second < rhs.second || (lhs.second == rhs.second && !lhs.first->isSyncMaster() && rhs.first->isSyncMaster()); + if(rhs->getPriority() != lhs->getPriority()) + return rhs->getPriority() > lhs->getPriority(); + else + return ((rhs->isSyncMaster() && !lhs->isSyncMaster()) || (rhs->isSyncSlave() && (!lhs->isSyncMaster() && !lhs->isSyncSlave()))); } }; @@ -319,10 +320,21 @@ void LLAudioEngine::idle(F32 max_decode_time) LLAudioSource *sync_masterp = NULL; //Highest priority syncmaster LLAudioSource *sync_slavep = NULL; //Highest priority syncslave + //Priority queue that will be filled with soundsources that have a high likeleyhood of being able to start [re]playing this idle tick. + //Sort order: Primary: priority. Secondary: syncmaster. Tertiary: syncslave + std::priority_queue >,SourcePriorityComparator> queue; + + //Have to put syncslaves into a temporary list until the syncmaster state is determined. + //If the syncmaster might be started, or just looped, insert all pending/looping syncslaves into the priority queue. + //If the there is no active syncmaster, then stop any currently looping syncslaves and add none to the priority queue. + //This is necessary as any looping soundsources to be stopped, MUST be stopped before iterating down the priority queue. + static std::vector slave_list; + slave_list.clear(); + //Iterate over all sources. Update their decode or 'done' state, as well as their priorities. - //Also add sources that might be able to start playing to a priority queue. - //Only sources without channels, or are waiting for a syncmaster, should be added to this queue. - std::priority_queue >,SourcePriorityComparator> queue; + //Also add sources that might be able to start playing to the priority queue. + //Only sources without channels should be added to priority queue. + //Syncslaves must put into the slave list instead of the priority queue. for (source_map::iterator iter = mAllSources.begin(); iter != mAllSources.end();) { LLAudioSource *sourcep = iter->second; @@ -360,34 +372,32 @@ void LLAudioEngine::idle(F32 max_decode_time) { if(!sync_masterp || sourcep->getPriority() > sync_masterp->getPriority()) { + if(sync_masterp && !sync_masterp->getChannel()) + queue.push(sync_masterp); //Add lower-priority soundmaster to the queue as a normal sound. sync_masterp = sourcep; + //Don't add master to the queue yet. + //Add it after highest-priority sound slave is found so we can outrank its priority. + continue; } - //This is a hack. Don't add master to the queue yet. - //Add it after highest-priority sound slave is found so we can outrank its priority. - continue; + //Else fall through like a normal sound. } else if(sourcep->isSyncSlave()) { - //Only care about syncslaves without channel, or are looping. - if(!sourcep->getChannel() || sourcep->isLoop() ) + if(!sync_slavep || sourcep->getPriority() > sync_slavep->getPriority()) { - if(!sync_slavep || sourcep->getPriority() > sync_slavep->getPriority()) - { - sync_slavep = sourcep; - } - } - else - { - continue; + sync_slavep = sourcep; } + //Don't add to the priority queue quite yet. Best primary syncmaster candidate may not have been determined yet. + slave_list.push_back(sourcep); + continue; } - else if(sourcep->getChannel()) + if(sourcep->getChannel()) { //If this is just a regular sound and is currently playing then do nothing special. continue; } //Add to our queue. Highest priority will be at the front. - queue.push(std::make_pair(sourcep,sourcep->getPriority())); + queue.push(sourcep); } // Do this BEFORE we update the channels @@ -395,47 +405,71 @@ void LLAudioEngine::idle(F32 max_decode_time) // such as changing what sound was playing. updateChannels(); - //Syncmaster must have priority greater than or equal to priority of highest priority syncslave. - if(sync_masterp && !sync_masterp->getChannel()) + //Loop over all syncsaves. If no syncmaster, stop looping ones, else add ones that need [re]started to the priority queue. + //Normally there are zero to one syncslaves, so this loop isn't typically expensive. + for(std::vector::iterator iter=slave_list.begin();iter!=slave_list.end();++iter) { + if(!sync_masterp) + { + //Stop any looping syncslaves. I'm not sure this behavior is correct, but letting looping syncslaves run + //off on their own seems wrong. The lsl documentation is unclear. + if((*iter)->getChannel() && (*iter)->isLoop()) + { + (*iter)->getChannel()->cleanup(); + } + } + //If the syncmaster already has a channel and hasn't looped, then we can skip syncslaves this idle tick (there's nothing to do). + else if((!sync_masterp->getChannel() || sync_masterp->getChannel()->mLoopedThisFrame)) + { + //Add syncslaves that we might want to [re]start. + if(!(*iter)->getChannel() || (*iter)->isLoop()) + queue.push((*iter)); + } + } + + if(sync_masterp) + { + //Syncmaster must have priority greater than or equal to priority of highest priority syncslave. //The case of slave priority equaling master priority is handled in the comparator (SourcePriorityComparator). - //Could have added an arbiturary small value to the priority, but the extra logic in the comparator is more correct. if(sync_slavep) - queue.push(std::make_pair(sync_masterp, llmax(sync_slavep->getPriority(), sync_masterp->getPriority()))); - else - queue.push(std::make_pair(sync_masterp, sync_masterp->getPriority())); + sync_masterp->setPriority(sync_slavep->getPriority()); + if(!sync_masterp->getChannel()) + { + queue.push(sync_masterp); + } } // Dispatch all soundsources. - bool syncmaster_started = false; + bool syncmaster_started = sync_masterp && sync_masterp->getChannel() && sync_masterp->getChannel()->mLoopedThisFrame; while(!queue.empty()) { - // This is lame, instead of this I could actually iterate through all the sources - // attached to each channel, since only those with active channels - // can have anything interesting happen with their queue? (Maybe not true) - LLAudioSource *sourcep = queue.top().first; + LLAudioSource *sourcep = queue.top(); queue.pop(); - if (sourcep->isSyncSlave()) + //If the syncmaster hasn't just started playing, or hasn't just looped then skip this soundslave, + //as there's nothing to do with it yet. + if (sourcep->isSyncSlave() && !syncmaster_started) { - //If the syncmaster hasn't just started playing, or hasn't just looped then skip this soundslave. - if(!sync_masterp || (!syncmaster_started && !(sync_masterp->getChannel() && sync_masterp->getChannel()->mLoopedThisFrame))) - continue; + continue; } LLAudioChannel *channelp = sourcep->getChannel(); if (!channelp) { - if(!(channelp = gAudiop->getFreeChannel(sourcep->getPriority()))) - continue; //No more channels. Don't abort the whole loop, however. Soundslaves might want to start up! - - //If this is the syncmaster that just started then we need to update non-playing syncslaves. - if(sourcep == sync_masterp) + //No more channels. We can break here, as in theory, any lower priority sounds should have had their + //channel already stolen. There should be nothing playing, nor playable, when iterating beyond this point. + if(!(channelp = getFreeChannel(sourcep->getPriority()))) { - syncmaster_started = true; + break; } - channelp->setSource(sourcep); //setSource calls updateBuffer and update3DPosition, and resets the source mAgeTimer + + //If this is the primary syncmaster that we just started, then [re]start syncslaves further down in the priority queue. + //Due to sorting and priority fudgery, the syncmaster is ALWAYS before any syncslaves in this loop. + syncmaster_started |= (sourcep == sync_masterp); + + //setSource calls updateBuffer and update3DPosition, and resets the source mAgeTimer + channelp->setSource(sourcep); } if(sourcep->isSyncSlave()) @@ -594,7 +628,7 @@ LLAudioChannel * LLAudioEngine::getFreeChannel(const F32 priority) } } - if (min_priority > priority || !min_channelp) + if (!min_channelp || min_priority >= priority) { // All playing channels have higher priority, return. return NULL; @@ -1411,11 +1445,11 @@ void LLAudioSource::updatePriority() { if (isAmbient()) { - mPriority = 1.f; + setPriority(1.f); } else if (isMuted()) { - mPriority = 0.f; + setPriority(0.f); } else { @@ -1425,7 +1459,7 @@ void LLAudioSource::updatePriority() dist_vec -= gAudiop->getListenerPos(); F32 dist_squared = llmax(1.f, dist_vec.magVecSquared()); - mPriority = mGain / dist_squared; + setPriority(mGain / dist_squared); } } diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index d1e23de96..4067f23a7 100644 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -316,6 +316,7 @@ public: LLVector3d getPositionGlobal() const { return mPositionGlobal; } LLVector3 getVelocity() const { return mVelocity; } F32 getPriority() const { return mPriority; } + void setPriority(F32 priority) { mPriority = priority; } // Gain should always be clamped between 0 and 1. F32 getGain() const { return mGain; } diff --git a/indra/llaudio/llaudioengine_fmod.cpp b/indra/llaudio/llaudioengine_fmod.cpp index 116a37314..01beaa9d5 100644 --- a/indra/llaudio/llaudioengine_fmod.cpp +++ b/indra/llaudio/llaudioengine_fmod.cpp @@ -590,6 +590,7 @@ void LLAudioChannelFMOD::cleanup() } mChannelID = 0; + mLastSamplePos = 0; } diff --git a/indra/llaudio/llaudioengine_fmodex.cpp b/indra/llaudio/llaudioengine_fmodex.cpp index 506c8d4ad..300264d91 100644 --- a/indra/llaudio/llaudioengine_fmodex.cpp +++ b/indra/llaudio/llaudioengine_fmodex.cpp @@ -810,6 +810,7 @@ void LLAudioChannelFMODEX::cleanup() Check_FMOD_Error(mChannelp->stop(),"FMOD::Channel::stop"); mChannelp = NULL; + mLastSamplePos = 0; } diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp index 27596c107..b511b9d28 100644 --- a/indra/llaudio/llaudioengine_openal.cpp +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -338,6 +338,7 @@ void LLAudioBufferOpenAL::cleanup() LL_WARNS("OpenAL") << "Error: " << alutGetErrorString( error ) << " when cleaning up a buffer" << LL_ENDL; } mALBuffer = AL_NONE; + mLastSamplePos = 0; } }