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.
This commit is contained in:
Shyotl
2013-10-16 21:32:08 -05:00
parent 4da896682d
commit 03c4535648
5 changed files with 86 additions and 48 deletions

View File

@@ -284,13 +284,14 @@ void LLAudioEngine::updateChannels()
checkStates(); checkStates();
} }
typedef std::pair<LLAudioSource*,F32> audio_source_t;
struct SourcePriorityComparator 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. if(rhs->getPriority() != lhs->getPriority())
return lhs.second < rhs.second || (lhs.second == rhs.second && !lhs.first->isSyncMaster() && rhs.first->isSyncMaster()); 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_masterp = NULL; //Highest priority syncmaster
LLAudioSource *sync_slavep = NULL; //Highest priority syncslave 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<LLAudioSource*,std::vector<LLAudioSource*,boost::pool_allocator<LLAudioSource*> >,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<LLAudioSource*> slave_list;
slave_list.clear();
//Iterate over all sources. Update their decode or 'done' state, as well as their priorities. //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. //Also add sources that might be able to start playing to the priority queue.
//Only sources without channels, or are waiting for a syncmaster, should be added to this queue. //Only sources without channels should be added to priority queue.
std::priority_queue<audio_source_t,std::vector<audio_source_t,boost::pool_allocator<audio_source_t> >,SourcePriorityComparator> queue; //Syncslaves must put into the slave list instead of the priority queue.
for (source_map::iterator iter = mAllSources.begin(); iter != mAllSources.end();) for (source_map::iterator iter = mAllSources.begin(); iter != mAllSources.end();)
{ {
LLAudioSource *sourcep = iter->second; 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 || 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; 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. //Else fall through like a normal sound.
//Add it after highest-priority sound slave is found so we can outrank its priority.
continue;
} }
else if(sourcep->isSyncSlave()) else if(sourcep->isSyncSlave())
{ {
//Only care about syncslaves without channel, or are looping. if(!sync_slavep || sourcep->getPriority() > sync_slavep->getPriority())
if(!sourcep->getChannel() || sourcep->isLoop() )
{ {
if(!sync_slavep || sourcep->getPriority() > sync_slavep->getPriority()) sync_slavep = sourcep;
{
sync_slavep = sourcep;
}
}
else
{
continue;
} }
//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. //If this is just a regular sound and is currently playing then do nothing special.
continue; continue;
} }
//Add to our queue. Highest priority will be at the front. //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 // 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. // such as changing what sound was playing.
updateChannels(); updateChannels();
//Syncmaster must have priority greater than or equal to priority of highest priority syncslave. //Loop over all syncsaves. If no syncmaster, stop looping ones, else add ones that need [re]started to the priority queue.
if(sync_masterp && !sync_masterp->getChannel()) //Normally there are zero to one syncslaves, so this loop isn't typically expensive.
for(std::vector<LLAudioSource*>::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). //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) if(sync_slavep)
queue.push(std::make_pair(sync_masterp, llmax(sync_slavep->getPriority(), sync_masterp->getPriority()))); sync_masterp->setPriority(sync_slavep->getPriority());
else if(!sync_masterp->getChannel())
queue.push(std::make_pair(sync_masterp, sync_masterp->getPriority())); {
queue.push(sync_masterp);
}
} }
// Dispatch all soundsources. // Dispatch all soundsources.
bool syncmaster_started = false; bool syncmaster_started = sync_masterp && sync_masterp->getChannel() && sync_masterp->getChannel()->mLoopedThisFrame;
while(!queue.empty()) while(!queue.empty())
{ {
// This is lame, instead of this I could actually iterate through all the sources LLAudioSource *sourcep = queue.top();
// 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;
queue.pop(); 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. continue;
if(!sync_masterp || (!syncmaster_started && !(sync_masterp->getChannel() && sync_masterp->getChannel()->mLoopedThisFrame)))
continue;
} }
LLAudioChannel *channelp = sourcep->getChannel(); LLAudioChannel *channelp = sourcep->getChannel();
if (!channelp) if (!channelp)
{ {
if(!(channelp = gAudiop->getFreeChannel(sourcep->getPriority()))) //No more channels. We can break here, as in theory, any lower priority sounds should have had their
continue; //No more channels. Don't abort the whole loop, however. Soundslaves might want to start up! //channel already stolen. There should be nothing playing, nor playable, when iterating beyond this point.
if(!(channelp = getFreeChannel(sourcep->getPriority())))
//If this is the syncmaster that just started then we need to update non-playing syncslaves.
if(sourcep == sync_masterp)
{ {
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()) 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. // All playing channels have higher priority, return.
return NULL; return NULL;
@@ -1411,11 +1445,11 @@ void LLAudioSource::updatePriority()
{ {
if (isAmbient()) if (isAmbient())
{ {
mPriority = 1.f; setPriority(1.f);
} }
else if (isMuted()) else if (isMuted())
{ {
mPriority = 0.f; setPriority(0.f);
} }
else else
{ {
@@ -1425,7 +1459,7 @@ void LLAudioSource::updatePriority()
dist_vec -= gAudiop->getListenerPos(); dist_vec -= gAudiop->getListenerPos();
F32 dist_squared = llmax(1.f, dist_vec.magVecSquared()); F32 dist_squared = llmax(1.f, dist_vec.magVecSquared());
mPriority = mGain / dist_squared; setPriority(mGain / dist_squared);
} }
} }

View File

@@ -316,6 +316,7 @@ public:
LLVector3d getPositionGlobal() const { return mPositionGlobal; } LLVector3d getPositionGlobal() const { return mPositionGlobal; }
LLVector3 getVelocity() const { return mVelocity; } LLVector3 getVelocity() const { return mVelocity; }
F32 getPriority() const { return mPriority; } F32 getPriority() const { return mPriority; }
void setPriority(F32 priority) { mPriority = priority; }
// Gain should always be clamped between 0 and 1. // Gain should always be clamped between 0 and 1.
F32 getGain() const { return mGain; } F32 getGain() const { return mGain; }

View File

@@ -590,6 +590,7 @@ void LLAudioChannelFMOD::cleanup()
} }
mChannelID = 0; mChannelID = 0;
mLastSamplePos = 0;
} }

View File

@@ -810,6 +810,7 @@ void LLAudioChannelFMODEX::cleanup()
Check_FMOD_Error(mChannelp->stop(),"FMOD::Channel::stop"); Check_FMOD_Error(mChannelp->stop(),"FMOD::Channel::stop");
mChannelp = NULL; mChannelp = NULL;
mLastSamplePos = 0;
} }

View File

@@ -338,6 +338,7 @@ void LLAudioBufferOpenAL::cleanup()
LL_WARNS("OpenAL") << "Error: " << alutGetErrorString( error ) << " when cleaning up a buffer" << LL_ENDL; LL_WARNS("OpenAL") << "Error: " << alutGetErrorString( error ) << " when cleaning up a buffer" << LL_ENDL;
} }
mALBuffer = AL_NONE; mALBuffer = AL_NONE;
mLastSamplePos = 0;
} }
} }