1887 lines
41 KiB
C++
1887 lines
41 KiB
C++
/**
|
|
* @file audioengine.cpp
|
|
* @brief implementation of LLAudioEngine class abstracting the Open
|
|
* AL audio support
|
|
*
|
|
* $LicenseInfo:firstyear=2000&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2010, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "llaudioengine.h"
|
|
#include "llstreamingaudio.h"
|
|
|
|
#include "llerror.h"
|
|
#include "llmath.h"
|
|
|
|
#include "sound_ids.h" // temporary hack for min/max distances
|
|
|
|
#include "llvfs.h"
|
|
#include "lldir.h"
|
|
#include "llaudiodecodemgr.h"
|
|
#include "llassetstorage.h"
|
|
|
|
#include <queue>
|
|
#include <boost/pool/pool_alloc.hpp>
|
|
|
|
|
|
// necessary for grabbing sounds from sim (implemented in viewer)
|
|
extern void request_sound(const LLUUID &sound_guid);
|
|
|
|
LLAudioEngine* gAudiop = NULL;
|
|
|
|
int gSoundHistoryPruneCounter = 0;
|
|
|
|
//
|
|
// LLAudioEngine implementation
|
|
//
|
|
|
|
|
|
LLAudioEngine::LLAudioEngine()
|
|
{
|
|
setDefaults();
|
|
}
|
|
|
|
|
|
LLAudioEngine::~LLAudioEngine()
|
|
{
|
|
}
|
|
|
|
LLStreamingAudioInterface* LLAudioEngine::getStreamingAudioImpl()
|
|
{
|
|
return mStreamingAudioImpl;
|
|
}
|
|
|
|
void LLAudioEngine::setStreamingAudioImpl(LLStreamingAudioInterface *impl)
|
|
{
|
|
mStreamingAudioImpl = impl;
|
|
}
|
|
|
|
void LLAudioEngine::setDefaults()
|
|
{
|
|
mMaxWindGain = 1.f;
|
|
|
|
mListenerp = NULL;
|
|
|
|
mMuted = false;
|
|
mUserData = NULL;
|
|
|
|
mLastStatus = 0;
|
|
|
|
mNumChannels = 0;
|
|
mEnableWind = false;
|
|
|
|
S32 i;
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
{
|
|
mChannels[i] = NULL;
|
|
}
|
|
for (i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
mBuffers[i] = NULL;
|
|
}
|
|
|
|
mMasterGain = 1.f;
|
|
// Setting mInternalGain to an out of range value fixes the issue reported in STORM-830.
|
|
// There is an edge case in setMasterGain during startup which prevents setInternalGain from
|
|
// being called if the master volume setting and mInternalGain both equal 0, so using -1 forces
|
|
// the if statement in setMasterGain to execute when the viewer starts up.
|
|
mInternalGain = -1.f;
|
|
mNextWindUpdate = 0.f;
|
|
|
|
mStreamingAudioImpl = NULL;
|
|
|
|
for (U32 i = 0; i < LLAudioEngine::AUDIO_TYPE_COUNT; i++)
|
|
mSecondaryGain[i] = 1.0f;
|
|
|
|
mCurrentTransfer = NULL;
|
|
|
|
mAllowLargeSounds = false;
|
|
}
|
|
|
|
|
|
bool LLAudioEngine::init(const S32 num_channels, void* userdata)
|
|
{
|
|
setDefaults();
|
|
|
|
mNumChannels = num_channels;
|
|
mUserData = userdata;
|
|
|
|
// Initialize the decode manager
|
|
gAudioDecodeMgrp = new LLAudioDecodeMgr;
|
|
|
|
LL_INFOS("AudioEngine") << "LLAudioEngine::init() AudioEngine successfully initialized" << LL_ENDL;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
void LLAudioEngine::shutdown()
|
|
{
|
|
// Clean up decode manager
|
|
delete gAudioDecodeMgrp;
|
|
gAudioDecodeMgrp = NULL;
|
|
|
|
// Clean up wind source
|
|
cleanupWind();
|
|
|
|
// Clean up audio sources
|
|
source_map::iterator iter_src;
|
|
for (iter_src = mAllSources.begin(); iter_src != mAllSources.end(); iter_src++)
|
|
{
|
|
delete iter_src->second;
|
|
}
|
|
|
|
|
|
// Clean up audio data
|
|
data_map::iterator iter_data;
|
|
for (iter_data = mAllData.begin(); iter_data != mAllData.end(); iter_data++)
|
|
{
|
|
delete iter_data->second;
|
|
}
|
|
|
|
|
|
// Clean up channels
|
|
S32 i;
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
{
|
|
delete mChannels[i];
|
|
mChannels[i] = NULL;
|
|
}
|
|
|
|
// Clean up buffers
|
|
for (i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
delete mBuffers[i];
|
|
mBuffers[i] = NULL;
|
|
}
|
|
|
|
delete mStreamingAudioImpl;
|
|
mStreamingAudioImpl = NULL;
|
|
}
|
|
|
|
|
|
// virtual
|
|
void LLAudioEngine::startInternetStream(const std::string& url)
|
|
{
|
|
if (mStreamingAudioImpl)
|
|
mStreamingAudioImpl->start(url);
|
|
}
|
|
|
|
|
|
// virtual
|
|
void LLAudioEngine::stopInternetStream()
|
|
{
|
|
if (mStreamingAudioImpl)
|
|
mStreamingAudioImpl->stop();
|
|
}
|
|
|
|
// virtual
|
|
void LLAudioEngine::pauseInternetStream(int pause)
|
|
{
|
|
if (mStreamingAudioImpl)
|
|
mStreamingAudioImpl->pause(pause);
|
|
}
|
|
|
|
// virtual
|
|
void LLAudioEngine::updateInternetStream()
|
|
{
|
|
if (mStreamingAudioImpl)
|
|
mStreamingAudioImpl->update();
|
|
}
|
|
|
|
// virtual
|
|
LLAudioEngine::LLAudioPlayState LLAudioEngine::isInternetStreamPlaying()
|
|
{
|
|
if (mStreamingAudioImpl)
|
|
return (LLAudioEngine::LLAudioPlayState) mStreamingAudioImpl->isPlaying();
|
|
|
|
return LLAudioEngine::AUDIO_STOPPED; // Stopped
|
|
}
|
|
|
|
|
|
// virtual
|
|
void LLAudioEngine::setInternetStreamGain(F32 vol)
|
|
{
|
|
if (mStreamingAudioImpl)
|
|
mStreamingAudioImpl->setGain(vol);
|
|
}
|
|
|
|
// virtual
|
|
std::string LLAudioEngine::getInternetStreamURL()
|
|
{
|
|
if (mStreamingAudioImpl)
|
|
return mStreamingAudioImpl->getURL();
|
|
else return std::string();
|
|
}
|
|
|
|
void LLAudioEngine::checkStates()
|
|
{
|
|
#ifdef SHOW_ASSERT
|
|
for (S32 i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
if (mBuffers[i])
|
|
{
|
|
bool buf_has_ref = false;
|
|
for (S32 j = 0; j < MAX_CHANNELS; j++)
|
|
{
|
|
if (mChannels[j])
|
|
{
|
|
if(mChannels[j]->mCurrentBufferp == mBuffers[i])
|
|
buf_has_ref = true;
|
|
}
|
|
}
|
|
if(buf_has_ref)
|
|
llassert(mBuffers[i]->mInUse);
|
|
}
|
|
}
|
|
#endif //SHOW_ASSERT
|
|
}
|
|
|
|
void LLAudioEngine::updateChannels()
|
|
{
|
|
S32 i;
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
{
|
|
if (mChannels[i])
|
|
{
|
|
mChannels[i]->updateBuffer();
|
|
mChannels[i]->update3DPosition();
|
|
mChannels[i]->updateLoop();
|
|
#ifdef SHOW_ASSERT
|
|
if(mChannels[i]->getSource())
|
|
llassert(mChannels[i]->mCurrentBufferp == mChannels[i]->getSource()->getCurrentBuffer());
|
|
if(mChannels[i]->mCurrentBufferp)
|
|
{
|
|
bool found_buffer = false;
|
|
for (S32 j = 0; j < MAX_BUFFERS; j++)
|
|
{
|
|
if (mBuffers[j])
|
|
{
|
|
if(mChannels[i]->mCurrentBufferp == mBuffers[j])
|
|
found_buffer = true;
|
|
}
|
|
}
|
|
llassert(found_buffer);
|
|
}
|
|
#endif //SHOW_ASSERT
|
|
}
|
|
}
|
|
checkStates();
|
|
}
|
|
|
|
struct SourcePriorityComparator
|
|
{
|
|
bool operator() (const LLAudioSource* lhs, const LLAudioSource* rhs) const
|
|
{
|
|
if(rhs->getPriority() != lhs->getPriority())
|
|
return rhs->getPriority() > lhs->getPriority();
|
|
else
|
|
return ((rhs->isSyncMaster() && !lhs->isSyncMaster()) || (rhs->isSyncSlave() && (!lhs->isSyncMaster() && !lhs->isSyncSlave())));
|
|
}
|
|
};
|
|
|
|
static const F32 default_max_decode_time = .002f; // 2 ms
|
|
void LLAudioEngine::idle(F32 max_decode_time)
|
|
{
|
|
if (max_decode_time <= 0.f)
|
|
{
|
|
max_decode_time = default_max_decode_time;
|
|
}
|
|
|
|
// "Update" all of our audio sources, clean up dead ones.
|
|
// Primarily does position updating, cleanup of unused audio sources.
|
|
// Also does regeneration of the current priority of each audio source.
|
|
|
|
S32 i;
|
|
for (i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
if (mBuffers[i])
|
|
{
|
|
mBuffers[i]->mInUse = false;
|
|
}
|
|
}
|
|
|
|
//Iterate down all queued sources. Remove finished ones, sort active ones by priority. Find highest priority 'master' source if present.
|
|
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<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.
|
|
//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;
|
|
|
|
//Load/decode/queue pop
|
|
sourcep->update();
|
|
|
|
//Done after update, as failure to load might mark source as corrupt, which causes isDone to return true.
|
|
if (sourcep->isDone())
|
|
{
|
|
// The source is done playing, clean it up.
|
|
delete sourcep;
|
|
mAllSources.erase(iter++);
|
|
continue;
|
|
}
|
|
|
|
// Increment iter here (it is not used anymore), so we can use continue below to move on to the next source.
|
|
++iter;
|
|
|
|
if(!sourcep->isLoop() && sourcep->mPlayedOnce && (!sourcep->mChannelp || !sourcep->mChannelp->isPlaying()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LLAudioData *adp = sourcep->getCurrentData();
|
|
//If there is no current data at all, or if it hasn't loaded, we must skip this source.
|
|
if (!adp || !adp->getBuffer())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
sourcep->updatePriority(); //Calculates current priority. 1.f=ambient. 0.f=muted. Otherwise based off of distance.
|
|
|
|
if (sourcep->getPriority() < F_APPROXIMATELY_ZERO)
|
|
{
|
|
// Muted, or nothing queued, or too far, so we don't care.
|
|
continue;
|
|
}
|
|
else if(sourcep->isSyncMaster())
|
|
{
|
|
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;
|
|
}
|
|
//Else fall through like a normal sound.
|
|
}
|
|
else if(sourcep->isSyncSlave())
|
|
{
|
|
if(!sync_slavep || sourcep->getPriority() > sync_slavep->getPriority())
|
|
{
|
|
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;
|
|
}
|
|
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(sourcep);
|
|
}
|
|
|
|
// Do this BEFORE we update the channels
|
|
// Update the channels to sync up with any changes that the source made,
|
|
// such as changing what sound was playing.
|
|
updateChannels();
|
|
|
|
//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<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).
|
|
if(sync_slavep)
|
|
sync_masterp->setPriority(sync_slavep->getPriority());
|
|
if(!sync_masterp->getChannel())
|
|
{
|
|
queue.push(sync_masterp);
|
|
}
|
|
}
|
|
|
|
// Dispatch all soundsources.
|
|
bool syncmaster_started = sync_masterp && sync_masterp->getChannel() && sync_masterp->getChannel()->mLoopedThisFrame;
|
|
while(!queue.empty())
|
|
{
|
|
LLAudioSource *sourcep = queue.top();
|
|
queue.pop();
|
|
|
|
//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)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LLAudioChannel *channelp = sourcep->getChannel();
|
|
|
|
if (!channelp)
|
|
{
|
|
//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())))
|
|
{
|
|
break;
|
|
}
|
|
|
|
//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())
|
|
channelp->playSynced(sync_masterp->getChannel());
|
|
else
|
|
channelp->play();
|
|
}
|
|
|
|
// Sync up everything that the audio engine needs done.
|
|
commitDeferredChanges();
|
|
|
|
// Flush unused buffers that are stale enough
|
|
for (i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
if (mBuffers[i])
|
|
{
|
|
if (!mBuffers[i]->mInUse && mBuffers[i]->mLastUseTimer.getElapsedTimeF32() > 30.f)
|
|
{
|
|
LL_DEBUGS("AudioEngine") << "Flushing unused buffer!" << LL_ENDL;
|
|
mBuffers[i]->mAudioDatap->mBufferp = NULL;
|
|
delete mBuffers[i];
|
|
mBuffers[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// Clear all of the looped flags for the channels
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
{
|
|
if (mChannels[i])
|
|
{
|
|
mChannels[i]->mLoopedThisFrame = false;
|
|
}
|
|
}
|
|
|
|
// Decode audio files
|
|
gAudioDecodeMgrp->processQueue(max_decode_time);
|
|
|
|
// Just call here every frame. It makes little sense to call elsewhere,
|
|
// as it's throttled to one active preloading loading sound at a time anyhow
|
|
startNextTransfer();
|
|
|
|
updateInternetStream();
|
|
}
|
|
|
|
void LLAudioEngine::enableWind(bool enable)
|
|
{
|
|
if (enable && (!mEnableWind))
|
|
{
|
|
mEnableWind = initWind();
|
|
}
|
|
else if (mEnableWind && (!enable))
|
|
{
|
|
mEnableWind = false;
|
|
cleanupWind();
|
|
}
|
|
}
|
|
|
|
|
|
LLAudioBuffer * LLAudioEngine::getFreeBuffer()
|
|
{
|
|
//checkStates(); //Fails
|
|
|
|
S32 i;
|
|
for (i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
if (!mBuffers[i])
|
|
{
|
|
mBuffers[i] = createBuffer();
|
|
return mBuffers[i];
|
|
}
|
|
}
|
|
|
|
//checkStates(); // Fails
|
|
|
|
// Grab the oldest unused buffer
|
|
F32 max_age = -1.f;
|
|
S32 buffer_id = -1;
|
|
for (i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
if (mBuffers[i])
|
|
{
|
|
if (!mBuffers[i]->mInUse)
|
|
{
|
|
if (mBuffers[i]->mLastUseTimer.getElapsedTimeF32() > max_age)
|
|
{
|
|
max_age = mBuffers[i]->mLastUseTimer.getElapsedTimeF32();
|
|
buffer_id = i;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//checkStates(); //Fails
|
|
|
|
if (buffer_id >= 0)
|
|
{
|
|
LL_DEBUGS("AudioEngine") << "Taking over unused buffer! max_age=" << max_age << LL_ENDL;
|
|
mBuffers[buffer_id]->mAudioDatap->mBufferp = NULL;
|
|
for (U32 i = 0; i < MAX_CHANNELS; i++)
|
|
{
|
|
LLAudioChannel* channelp = mChannels[i];
|
|
if(channelp && channelp->mCurrentBufferp == mBuffers[buffer_id])
|
|
{
|
|
channelp->cleanup();
|
|
llassert(channelp->mCurrentBufferp == NULL);
|
|
}
|
|
}
|
|
delete mBuffers[buffer_id];
|
|
mBuffers[buffer_id] = createBuffer();
|
|
return mBuffers[buffer_id];
|
|
}
|
|
|
|
//checkStates(); //Fails
|
|
return NULL;
|
|
}
|
|
|
|
|
|
LLAudioChannel * LLAudioEngine::getFreeChannel(const F32 priority)
|
|
{
|
|
S32 i;
|
|
for (i = 0; i < mNumChannels; i++)
|
|
{
|
|
if (!mChannels[i])
|
|
{
|
|
// No channel allocated here, use it.
|
|
mChannels[i] = createChannel();
|
|
return mChannels[i];
|
|
}
|
|
else
|
|
{
|
|
// Channel is allocated but not playing right now, use it.
|
|
if (mChannels[i]->isFree())
|
|
{
|
|
LL_DEBUGS("AudioEngine") << "Replacing unused channel" << LL_ENDL;
|
|
return mChannels[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
// All channels used, check priorities.
|
|
// Find channel with lowest priority and see if we want to replace it.
|
|
F32 min_priority = 10000.f;
|
|
LLAudioChannel *min_channelp = NULL;
|
|
|
|
for (i = 0; i < mNumChannels; i++)
|
|
{
|
|
LLAudioChannel *channelp = mChannels[i];
|
|
LLAudioSource *sourcep = channelp->getSource();
|
|
if (sourcep->getPriority() < min_priority)
|
|
{
|
|
min_channelp = channelp;
|
|
min_priority = sourcep->getPriority();
|
|
}
|
|
}
|
|
|
|
if (!min_channelp || min_priority >= priority)
|
|
{
|
|
// All playing channels have higher priority, return.
|
|
return NULL;
|
|
}
|
|
|
|
LL_DEBUGS("AudioEngine") << "Flushing min channel" << LL_ENDL;
|
|
// Flush the minimum priority channel, and return it.
|
|
min_channelp->cleanup();
|
|
return min_channelp;
|
|
}
|
|
|
|
|
|
void LLAudioEngine::cleanupBuffer(LLAudioBuffer *bufferp)
|
|
{
|
|
S32 i;
|
|
for (i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
if (mBuffers[i] == bufferp)
|
|
{
|
|
delete mBuffers[i];
|
|
mBuffers[i] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
bool LLAudioEngine::preloadSound(const LLUUID &uuid)
|
|
{
|
|
if(uuid.isNull())
|
|
return false;
|
|
|
|
if(getAudioData(uuid)->getLoadState() >= LLAudioData::STATE_LOAD_DECODING)
|
|
{
|
|
// This means that we do have a local copy, and we're working on decoding it.
|
|
return true;
|
|
}
|
|
|
|
LL_INFOS("AudioEngine") << "Preloading system sound " << uuid << LL_ENDL;
|
|
mPreloadSystemList.push_back(uuid);
|
|
|
|
// At some point we need to have the audio/asset system check the static VFS
|
|
// before it goes off and fetches stuff from the server.
|
|
//LL_INFOS("AudioEngine") << "Used internal preload for non-local sound" << LL_ENDL;
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LLAudioEngine::isWindEnabled()
|
|
{
|
|
return mEnableWind;
|
|
}
|
|
|
|
|
|
void LLAudioEngine::setMuted(bool muted)
|
|
{
|
|
if (muted != mMuted)
|
|
{
|
|
mMuted = muted;
|
|
setMasterGain(mMasterGain);
|
|
}
|
|
enableWind(!mMuted);
|
|
}
|
|
|
|
void LLAudioEngine::setMasterGain(const F32 gain)
|
|
{
|
|
mMasterGain = gain;
|
|
F32 internal_gain = getMuted() ? 0.f : gain;
|
|
if (internal_gain != mInternalGain)
|
|
{
|
|
mInternalGain = internal_gain;
|
|
setInternalGain(mInternalGain);
|
|
}
|
|
}
|
|
|
|
F32 LLAudioEngine::getMasterGain()
|
|
{
|
|
return mMasterGain;
|
|
}
|
|
|
|
void LLAudioEngine::setSecondaryGain(S32 type, F32 gain)
|
|
{
|
|
llassert(type < LLAudioEngine::AUDIO_TYPE_COUNT);
|
|
|
|
mSecondaryGain[type] = gain;
|
|
}
|
|
|
|
F32 LLAudioEngine::getSecondaryGain(S32 type)
|
|
{
|
|
return mSecondaryGain[type];
|
|
}
|
|
|
|
F32 LLAudioEngine::getInternetStreamGain()
|
|
{
|
|
if (mStreamingAudioImpl)
|
|
return mStreamingAudioImpl->getGain();
|
|
else
|
|
return 1.0f;
|
|
}
|
|
|
|
void LLAudioEngine::setMaxWindGain(F32 gain)
|
|
{
|
|
mMaxWindGain = gain;
|
|
}
|
|
|
|
|
|
F64 LLAudioEngine::mapWindVecToGain(LLVector3 wind_vec)
|
|
{
|
|
F64 gain = 0.0;
|
|
|
|
gain = wind_vec.magVec();
|
|
|
|
if (gain)
|
|
{
|
|
if (gain > 20)
|
|
{
|
|
gain = 20;
|
|
}
|
|
gain = gain/20.0;
|
|
}
|
|
|
|
return (gain);
|
|
}
|
|
|
|
|
|
F64 LLAudioEngine::mapWindVecToPitch(LLVector3 wind_vec)
|
|
{
|
|
LLVector3 listen_right;
|
|
F64 theta;
|
|
|
|
// Wind frame is in listener-relative coordinates
|
|
LLVector3 norm_wind = wind_vec;
|
|
norm_wind.normVec();
|
|
listen_right.setVec(1.0,0.0,0.0);
|
|
|
|
// measure angle between wind vec and listener right axis (on 0,PI)
|
|
theta = acos(norm_wind * listen_right);
|
|
|
|
// put it on 0, 1
|
|
theta /= F_PI;
|
|
|
|
// put it on [0, 0.5, 0]
|
|
if (theta > 0.5) theta = 1.0-theta;
|
|
if (theta < 0) theta = 0;
|
|
|
|
return (theta);
|
|
}
|
|
|
|
|
|
F64 LLAudioEngine::mapWindVecToPan(LLVector3 wind_vec)
|
|
{
|
|
LLVector3 listen_right;
|
|
F64 theta;
|
|
|
|
// Wind frame is in listener-relative coordinates
|
|
listen_right.setVec(1.0,0.0,0.0);
|
|
|
|
LLVector3 norm_wind = wind_vec;
|
|
norm_wind.normVec();
|
|
|
|
// measure angle between wind vec and listener right axis (on 0,PI)
|
|
theta = acos(norm_wind * listen_right);
|
|
|
|
// put it on 0, 1
|
|
theta /= F_PI;
|
|
|
|
return (theta);
|
|
}
|
|
|
|
|
|
void LLAudioEngine::triggerSound(const LLUUID &audio_uuid, const LLUUID& owner_id, const F32 gain,
|
|
// <edit>
|
|
//const S32 type, const LLVector3d &pos_global)
|
|
const S32 type, const LLVector3d &pos_global, const LLUUID source_object)
|
|
// </edit>
|
|
{
|
|
if(!mListenerp)
|
|
return;
|
|
// Create a new source (since this can't be associated with an existing source.
|
|
//LL_INFOS("AudioEngine") << "Localized: " << audio_uuid << LL_ENDL;
|
|
|
|
if (mMuted || gain < FLT_EPSILON*2)
|
|
{
|
|
return;
|
|
}
|
|
|
|
LLUUID source_id;
|
|
source_id.generate();
|
|
|
|
// <edit>
|
|
//LLAudioSource *asp = new LLAudioSource(source_id, owner_id, gain, type);
|
|
LLAudioSource *asp = new LLAudioSource(source_id, owner_id, gain, type, source_object, true);
|
|
// </edit>
|
|
gAudiop->addAudioSource(asp);
|
|
if (pos_global.isExactlyZero())
|
|
{
|
|
asp->setAmbient(true);
|
|
}
|
|
else
|
|
{
|
|
asp->setPositionGlobal(pos_global);
|
|
}
|
|
asp->updatePriority();
|
|
asp->play(audio_uuid);
|
|
}
|
|
|
|
|
|
void LLAudioEngine::setListenerPos(LLVector3 aVec)
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
mListenerp->setPosition(aVec);
|
|
}
|
|
}
|
|
|
|
|
|
LLVector3 LLAudioEngine::getListenerPos()
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
return mListenerp->getPosition();
|
|
}
|
|
else
|
|
{
|
|
return(LLVector3::zero);
|
|
}
|
|
}
|
|
|
|
|
|
void LLAudioEngine::setListenerVelocity(LLVector3 aVec)
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
mListenerp->setVelocity(aVec);
|
|
}
|
|
}
|
|
|
|
|
|
void LLAudioEngine::translateListener(LLVector3 aVec)
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
mListenerp->translate(aVec);
|
|
}
|
|
}
|
|
|
|
|
|
void LLAudioEngine::orientListener(LLVector3 up, LLVector3 at)
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
mListenerp->orient(up, at);
|
|
}
|
|
}
|
|
|
|
|
|
void LLAudioEngine::setListener(LLVector3 pos, LLVector3 vel, LLVector3 up, LLVector3 at)
|
|
{
|
|
if(!mListenerp)
|
|
{
|
|
allocateListener();
|
|
}
|
|
mListenerp->set(pos,vel,up,at);
|
|
}
|
|
|
|
|
|
void LLAudioEngine::setDopplerFactor(F32 factor)
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
mListenerp->setDopplerFactor(factor);
|
|
}
|
|
}
|
|
|
|
|
|
F32 LLAudioEngine::getDopplerFactor()
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
return mListenerp->getDopplerFactor();
|
|
}
|
|
else
|
|
{
|
|
return 0.f;
|
|
}
|
|
}
|
|
|
|
|
|
void LLAudioEngine::setRolloffFactor(F32 factor)
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
mListenerp->setRolloffFactor(factor);
|
|
}
|
|
}
|
|
|
|
|
|
F32 LLAudioEngine::getRolloffFactor()
|
|
{
|
|
if (mListenerp)
|
|
{
|
|
return mListenerp->getRolloffFactor();
|
|
}
|
|
else
|
|
{
|
|
return 0.f;
|
|
}
|
|
}
|
|
|
|
|
|
void LLAudioEngine::commitDeferredChanges()
|
|
{
|
|
if(mListenerp)
|
|
{
|
|
mListenerp->commitDeferredChanges();
|
|
}
|
|
}
|
|
|
|
|
|
LLAudioSource * LLAudioEngine::findAudioSource(const LLUUID &source_id)
|
|
{
|
|
source_map::iterator iter;
|
|
iter = mAllSources.find(source_id);
|
|
|
|
if (iter == mAllSources.end())
|
|
{
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
return iter->second;
|
|
}
|
|
}
|
|
|
|
|
|
LLAudioData * LLAudioEngine::getAudioData(const LLUUID &audio_uuid)
|
|
{
|
|
data_map::iterator iter;
|
|
iter = mAllData.find(audio_uuid);
|
|
if (iter == mAllData.end())
|
|
{
|
|
// Create the new audio data
|
|
LLAudioData *adp = new LLAudioData(audio_uuid);
|
|
mAllData[audio_uuid] = adp;
|
|
return adp;
|
|
}
|
|
else
|
|
{
|
|
return iter->second;
|
|
}
|
|
}
|
|
|
|
void LLAudioEngine::removeAudioData(LLUUID &audio_uuid)
|
|
{
|
|
if(audio_uuid.isNull())
|
|
return;
|
|
data_map::iterator iter = mAllData.find(audio_uuid);
|
|
if(iter != mAllData.end())
|
|
{
|
|
|
|
for (source_map::iterator iter2 = mAllSources.begin(); iter2 != mAllSources.end();)
|
|
{
|
|
LLAudioSource *sourcep = iter2->second;
|
|
if( sourcep && sourcep->getCurrentData() && sourcep->getCurrentData()->getID() == audio_uuid )
|
|
{
|
|
LLAudioChannel* chan=sourcep->getChannel();
|
|
if(chan)
|
|
{
|
|
LL_DEBUGS("AudioEngine") << "removeAudioData" << LL_ENDL;
|
|
chan->cleanup();
|
|
}
|
|
delete sourcep;
|
|
mAllSources.erase(iter2++);
|
|
}
|
|
else
|
|
++iter2;
|
|
}
|
|
if(iter->second) //Shouldn't be null, but playing it safe.
|
|
{
|
|
LLAudioBuffer* buf=((LLAudioData*)iter->second)->getBuffer();
|
|
if(buf)
|
|
{
|
|
for (S32 i = 0; i < MAX_BUFFERS; i++)
|
|
{
|
|
if(mBuffers[i] == buf)
|
|
mBuffers[i] = NULL;
|
|
}
|
|
delete buf;
|
|
}
|
|
delete iter->second;
|
|
}
|
|
mAllData.erase(iter);
|
|
}
|
|
}
|
|
void LLAudioEngine::addAudioSource(LLAudioSource *asp)
|
|
{
|
|
mAllSources[asp->getID()] = asp;
|
|
}
|
|
|
|
|
|
void LLAudioEngine::cleanupAudioSource(LLAudioSource *asp)
|
|
{
|
|
source_map::iterator iter;
|
|
iter = mAllSources.find(asp->getID());
|
|
if (iter == mAllSources.end())
|
|
{
|
|
LL_WARNS("AudioEngine") << "Cleaning up unknown audio source!" << LL_ENDL;
|
|
return;
|
|
}
|
|
delete asp;
|
|
mAllSources.erase(iter);
|
|
}
|
|
|
|
|
|
bool LLAudioEngine::hasDecodedFile(const LLUUID &uuid)
|
|
{
|
|
std::string uuid_str;
|
|
uuid.toString(uuid_str);
|
|
|
|
std::string wav_path;
|
|
wav_path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str);
|
|
wav_path += ".dsf";
|
|
|
|
if (gDirUtilp->fileExists(wav_path))
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void LLAudioEngine::startNextTransfer()
|
|
{
|
|
//LL_INFOS("AudioEngine") << "LLAudioEngine::startNextTransfer()" << LL_ENDL;
|
|
if (getMuted())
|
|
{
|
|
return;
|
|
}
|
|
else if(mCurrentTransferTimer.getElapsedTimeF32() <= .1f)
|
|
{
|
|
return;
|
|
}
|
|
else if(mCurrentTransfer && mCurrentTransfer->isInPreload())
|
|
{
|
|
//Keep updating until it either errors out or completes.
|
|
mCurrentTransfer->updateLoadState();
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
mCurrentTransfer = NULL;
|
|
}
|
|
|
|
//Technically, mCurrentTransfer could end up pointing to an audiodata object that's already
|
|
//being transmitted/decoded if such was spawned via needing it for playback immediately.
|
|
//This will effectively block us from choosing a lower priority audiodata object until the
|
|
//immediate ones are done, but it's not a real problem.
|
|
|
|
// Get the ID for the next asset that we want to transfer.
|
|
// Pick one in the following order:
|
|
S32 i;
|
|
LLAudioSource *asp = NULL;
|
|
LLAudioData *adp = NULL;
|
|
LLAudioData *cur_adp = NULL;
|
|
data_map::iterator data_iter;
|
|
|
|
// Check all channels for currently playing sounds.
|
|
F32 max_pri = -1.f;
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
{
|
|
if (!mChannels[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
asp = mChannels[i]->getSource();
|
|
if (!asp)
|
|
{
|
|
continue;
|
|
}
|
|
if (asp->getPriority() <= max_pri)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (asp->getPriority() <= max_pri)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
adp = asp->getCurrentData();
|
|
if (!adp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (adp->isInPreload())
|
|
{
|
|
max_pri = asp->getPriority();
|
|
cur_adp = adp;
|
|
}
|
|
}
|
|
|
|
// Check all channels for currently queued sounds.
|
|
if (!cur_adp)
|
|
{
|
|
max_pri = -1.f;
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
{
|
|
if (!mChannels[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LLAudioSource *asp;
|
|
asp = mChannels[i]->getSource();
|
|
if (!asp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (asp->getPriority() <= max_pri)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
adp = asp->getQueuedData();
|
|
if (!adp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (adp->isInPreload())
|
|
{
|
|
max_pri = asp->getPriority();
|
|
cur_adp = adp;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check all live channels for other sounds (preloads).
|
|
if (!cur_adp)
|
|
{
|
|
max_pri = -1.f;
|
|
for (i = 0; i < MAX_CHANNELS; i++)
|
|
{
|
|
if (!mChannels[i])
|
|
{
|
|
continue;
|
|
}
|
|
|
|
LLAudioSource *asp;
|
|
asp = mChannels[i]->getSource();
|
|
if (!asp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (asp->getPriority() <= max_pri)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
|
|
for (data_iter = asp->mPreloadMap.begin(); data_iter != asp->mPreloadMap.end(); data_iter++)
|
|
{
|
|
LLAudioData *adp = data_iter->second;
|
|
if (!adp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (adp->isInPreload())
|
|
{
|
|
max_pri = asp->getPriority();
|
|
cur_adp = adp;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Check all sources
|
|
if (!cur_adp)
|
|
{
|
|
max_pri = -1.f;
|
|
source_map::iterator source_iter;
|
|
for (source_iter = mAllSources.begin(); source_iter != mAllSources.end(); source_iter++)
|
|
{
|
|
asp = source_iter->second;
|
|
if (!asp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (asp->getPriority() <= max_pri)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
adp = asp->getCurrentData();
|
|
if (adp && adp->isInPreload())
|
|
{
|
|
max_pri = asp->getPriority();
|
|
cur_adp = adp;
|
|
continue;
|
|
}
|
|
|
|
adp = asp->getQueuedData();
|
|
if (adp && adp->isInPreload())
|
|
{
|
|
max_pri = asp->getPriority();
|
|
cur_adp = adp;
|
|
continue;
|
|
}
|
|
|
|
for (data_iter = asp->mPreloadMap.begin(); data_iter != asp->mPreloadMap.end(); data_iter++)
|
|
{
|
|
LLAudioData *adp = data_iter->second;
|
|
if (!adp)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (adp->isInPreload())
|
|
{
|
|
max_pri = asp->getPriority();
|
|
cur_adp = adp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!cur_adp)
|
|
{
|
|
while(!mPreloadSystemList.empty())
|
|
{
|
|
adp = getAudioData(mPreloadSystemList.front());
|
|
mPreloadSystemList.pop_front();
|
|
if(adp->isInPreload())
|
|
{
|
|
cur_adp = adp;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if(cur_adp)
|
|
{
|
|
std::list<LLUUID>::iterator it = std::find(mPreloadSystemList.begin(),mPreloadSystemList.end(),cur_adp->getID());
|
|
if(it != mPreloadSystemList.end())
|
|
mPreloadSystemList.erase(it);
|
|
}
|
|
|
|
if (cur_adp)
|
|
{
|
|
mCurrentTransfer = cur_adp;
|
|
mCurrentTransferTimer.reset();
|
|
mCurrentTransfer->updateLoadState();
|
|
}
|
|
else
|
|
{
|
|
//LL_INFOS("AudioEngine") << "No pending transfers?" << LL_ENDL;
|
|
}
|
|
}
|
|
|
|
|
|
// static
|
|
void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status)
|
|
{
|
|
if(!gAudiop)
|
|
return;
|
|
|
|
LLAudioData *adp = gAudiop->getAudioData(uuid);
|
|
|
|
if (result_code)
|
|
{
|
|
LL_INFOS("AudioEngine") << "Boom, error in audio file transfer: " << LLAssetStorage::getErrorString( result_code ) << " (" << result_code << ")" << LL_ENDL;
|
|
// Need to mark data as bad to avoid constant rerequests.
|
|
|
|
if (adp)
|
|
{
|
|
adp->setLoadState(LLAudioData::STATE_LOAD_ERROR);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (!adp)
|
|
{
|
|
// Should never happen
|
|
LL_WARNS("AudioEngine") << "Got asset callback without audio data for " << uuid << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
// LL_INFOS("AudioEngine") << "Got asset callback with good audio data for " << uuid << ", making decode request" << LL_ENDL;
|
|
adp->setLoadState(LLAudioData::STATE_LOAD_REQ_DECODE);
|
|
//Immediate decode.
|
|
adp->updateLoadState();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//
|
|
// LLAudioSource implementation
|
|
//
|
|
|
|
|
|
// <edit>
|
|
//LLAudioSource::LLAudioSource(const LLUUID& id, const LLUUID& owner_id, const F32 gain, const S32 type)
|
|
LLAudioSource::LLAudioSource(const LLUUID& id, const LLUUID& owner_id, const F32 gain, const S32 type, const LLUUID source_id, const bool isTrigger)
|
|
// </edit>
|
|
: mID(id),
|
|
mOwnerID(owner_id),
|
|
mPriority(0.f),
|
|
mGain(gain),
|
|
mSourceMuted(false),
|
|
mAmbient(false),
|
|
mLoop(false),
|
|
mSyncMaster(false),
|
|
mSyncSlave(false),
|
|
mQueueSounds(false),
|
|
mPlayedOnce(false),
|
|
mCorrupted(false),
|
|
mType(type),
|
|
// <edit>
|
|
mSourceID(source_id),
|
|
mIsTrigger(isTrigger),
|
|
// </edit>
|
|
mChannelp(NULL),
|
|
mCurrentDatap(NULL),
|
|
mQueuedDatap(NULL)
|
|
{
|
|
// <edit>
|
|
mLogID.generate();
|
|
// </edit>
|
|
}
|
|
|
|
|
|
LLAudioSource::~LLAudioSource()
|
|
{
|
|
// <edit>
|
|
// Record destruction of LLAudioSource object.
|
|
if(mType != LLAudioEngine::AUDIO_TYPE_UI)
|
|
logSoundStop(mLogID, true);
|
|
// </edit>
|
|
if (mChannelp)
|
|
{
|
|
// Stop playback of this sound
|
|
mChannelp->cleanup();
|
|
}
|
|
}
|
|
|
|
|
|
void LLAudioSource::setChannel(LLAudioChannel *channelp)
|
|
{
|
|
if (channelp == mChannelp)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//Either this channel just finished playing, or just started. Either way, reset the age timer.
|
|
mAgeTimer.reset();
|
|
|
|
// <edit>
|
|
if (!channelp)
|
|
{
|
|
if(mType != LLAudioEngine::AUDIO_TYPE_UI)
|
|
logSoundStop(mLogID, false);
|
|
}
|
|
// </edit>
|
|
|
|
mChannelp = channelp;
|
|
}
|
|
|
|
void LLAudioSource::update()
|
|
{
|
|
if(mCorrupted)
|
|
{
|
|
return ; //no need to update
|
|
}
|
|
|
|
// If data is queued up and we aren't playing it, shuffle it to current and try to load it.
|
|
if(isQueueSounds() && mPlayedOnce && mQueuedDatap && !mChannelp)
|
|
{
|
|
mCurrentDatap = mQueuedDatap;
|
|
mQueuedDatap = NULL;
|
|
|
|
//Make sure this source looks like its brand new again to prevent removal.
|
|
mPlayedOnce = false;
|
|
mAgeTimer.reset();
|
|
}
|
|
|
|
LLAudioData *adp = getCurrentData();
|
|
if (adp && !adp->getBuffer())
|
|
{
|
|
if(adp->getLoadState() == LLAudioData::STATE_LOAD_ERROR)
|
|
{
|
|
LL_WARNS("AudioEngine") << "Marking LLAudioSource corrupted for " << adp->getID() << LL_ENDL;
|
|
mCorrupted = true ;
|
|
}
|
|
else if(adp->getLoadState() == LLAudioData::STATE_LOAD_READY)
|
|
{
|
|
// Update the audio buffer first - load a sound if we have it.
|
|
// Note that this could potentially cause us to waste time updating buffers
|
|
// for sounds that actually aren't playing, although this should be mitigated
|
|
// by the fact that we limit the number of buffers, and we flush buffers based
|
|
// on priority.
|
|
adp->load(); //If it fails, just try again next update.
|
|
}
|
|
else
|
|
{
|
|
//The sound wasn't preloaded yet... so we must kick off the process.
|
|
adp->updateLoadState();
|
|
}
|
|
}
|
|
}
|
|
|
|
void LLAudioSource::updatePriority()
|
|
{
|
|
if (isAmbient())
|
|
{
|
|
setPriority(1.f);
|
|
}
|
|
else if (isMuted())
|
|
{
|
|
setPriority(0.f);
|
|
}
|
|
else
|
|
{
|
|
// Priority is based on distance
|
|
LLVector3 dist_vec;
|
|
dist_vec.setVec(getPositionGlobal());
|
|
dist_vec -= gAudiop->getListenerPos();
|
|
F32 dist_squared = llmax(1.f, dist_vec.magVecSquared());
|
|
|
|
setPriority(mGain / dist_squared);
|
|
}
|
|
}
|
|
|
|
bool LLAudioSource::play(const LLUUID &audio_uuid)
|
|
{
|
|
// Special abuse of play(); don't play a sound, but kill it.
|
|
if (audio_uuid.isNull())
|
|
{
|
|
if (getChannel())
|
|
{
|
|
llassert(this == getChannel()->getSource());
|
|
getChannel()->cleanup();
|
|
if (!isMuted())
|
|
{
|
|
mCurrentDatap = NULL;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// <edit>
|
|
if(mType != LLAudioEngine::AUDIO_TYPE_UI) //&& mSourceID.notNull())
|
|
logSoundPlay(this, audio_uuid);
|
|
// </edit>
|
|
|
|
// Reset our age timeout if someone attempts to play the source.
|
|
mAgeTimer.reset();
|
|
|
|
LLAudioData *adp = gAudiop->getAudioData(audio_uuid);
|
|
|
|
if (isQueueSounds())
|
|
{
|
|
if(mQueuedDatap)
|
|
{
|
|
// We already have a sound queued up. Ignore new request.
|
|
return false;
|
|
}
|
|
else if (adp == mCurrentDatap && isLoop())
|
|
{
|
|
// No point in queuing the same sound if
|
|
// we're looping.
|
|
return true;
|
|
}
|
|
else if(mCurrentDatap)
|
|
{
|
|
mQueuedDatap = adp;
|
|
return true;
|
|
}
|
|
}
|
|
else if(mCurrentDatap == adp) //Desired sound did not change. Just re-play it.
|
|
{
|
|
if(getChannel() && getChannel()->isPlaying())
|
|
getChannel()->play();
|
|
return true;
|
|
}
|
|
else //Desired sound did change. Release the old channel if set.
|
|
{
|
|
if(getChannel())
|
|
getChannel()->cleanup();
|
|
mPlayedOnce = false; //Reset the played flag so the new sound is actually started up.
|
|
}
|
|
|
|
mCurrentDatap = adp;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool LLAudioSource::isDone() const
|
|
{
|
|
static const F32 MAX_AGE = 60.f;
|
|
static const F32 MAX_UNPLAYED_AGE = 15.f;
|
|
static const F32 MAX_MUTED_AGE = 11.f;
|
|
if(mCorrupted)
|
|
{
|
|
// If we decode bad data then just kill this source entirely.
|
|
return true;
|
|
}
|
|
else if (isLoop())
|
|
{
|
|
// Looped sources never die on their own.
|
|
return false;
|
|
}
|
|
else if (hasPendingPreloads())
|
|
{
|
|
// If there are pending preload requests then keep it alive.
|
|
return false;
|
|
}
|
|
else if (mQueuedDatap)
|
|
{
|
|
// Don't kill this sound if we've got something queued up to play.
|
|
return false;
|
|
}
|
|
else if(mPlayedOnce && (!mChannelp || !mChannelp->isPlaying()))
|
|
{
|
|
// This is a single-play source and it already did its thing.
|
|
return true;
|
|
}
|
|
|
|
F32 elapsed = mAgeTimer.getElapsedTimeF32();
|
|
|
|
if (!mChannelp)
|
|
{
|
|
LLAudioData* adp = mCurrentDatap;
|
|
|
|
//Still decoding.
|
|
if(adp && adp->isInPreload())
|
|
return false;
|
|
|
|
// We don't have a channel assigned, and it's been
|
|
// over 15 seconds since we tried to play it. Don't bother.
|
|
return (elapsed > (mSourceMuted ? MAX_MUTED_AGE : MAX_UNPLAYED_AGE));
|
|
}
|
|
else if (mChannelp->isPlaying())
|
|
{
|
|
// Arbitarily cut off non-looped sounds when they're old.
|
|
return elapsed > MAX_AGE;
|
|
}
|
|
else if(!isSyncSlave())
|
|
{
|
|
// The sound isn't playing back after 15 seconds, kill it.
|
|
// This might happen if all channels are in use and this source is low-priority
|
|
return elapsed > MAX_UNPLAYED_AGE;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
void LLAudioSource::preload(const LLUUID &audio_id)
|
|
{
|
|
if(audio_id.notNull())
|
|
{
|
|
// Add it to the preload list.
|
|
mPreloadMap[audio_id] = gAudiop->getAudioData(audio_id);
|
|
}
|
|
}
|
|
|
|
|
|
bool LLAudioSource::hasPendingPreloads() const
|
|
{
|
|
// Check to see if we've got any preloads on deck for this source
|
|
data_map::const_iterator iter;
|
|
for (iter = mPreloadMap.begin(); iter != mPreloadMap.end(); iter++)
|
|
{
|
|
LLAudioData *adp = iter->second;
|
|
// note: a bad UUID will forever be !hasDecodedData()
|
|
// but also !hasValidData(), hence the check for hasValidData()
|
|
if (!adp)
|
|
{
|
|
continue;
|
|
}
|
|
if (adp->isInPreload())
|
|
{
|
|
// This source is still waiting for a preload
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
LLAudioData * LLAudioSource::getCurrentData()
|
|
{
|
|
return mCurrentDatap;
|
|
}
|
|
|
|
LLAudioData * LLAudioSource::getQueuedData()
|
|
{
|
|
return mQueuedDatap;
|
|
}
|
|
|
|
LLAudioBuffer * LLAudioSource::getCurrentBuffer()
|
|
{
|
|
if (!mCurrentDatap)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
return mCurrentDatap->getBuffer();
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// LLAudioChannel implementation
|
|
//
|
|
|
|
|
|
LLAudioChannel::LLAudioChannel() :
|
|
mCurrentSourcep(NULL),
|
|
mCurrentBufferp(NULL),
|
|
mLoopedThisFrame(false),
|
|
mSecondaryGain(1.0f)
|
|
{
|
|
}
|
|
|
|
LLAudioChannel::~LLAudioChannel()
|
|
{
|
|
llassert(mCurrentBufferp == NULL);
|
|
|
|
// Need to disconnect any sources which are using this channel.
|
|
//LL_INFOS("AudioEngine") << "Cleaning up audio channel" << LL_ENDL;
|
|
cleanup();
|
|
}
|
|
|
|
void LLAudioChannel::cleanup()
|
|
{
|
|
if(mCurrentSourcep)
|
|
mCurrentSourcep->setChannel(NULL);
|
|
mCurrentBufferp = NULL;
|
|
mCurrentSourcep = NULL;
|
|
}
|
|
|
|
|
|
void LLAudioChannel::setSource(LLAudioSource *sourcep)
|
|
{
|
|
llassert_always(sourcep);
|
|
llassert_always(!mCurrentSourcep);
|
|
|
|
mCurrentSourcep = sourcep;
|
|
mCurrentSourcep->setChannel(this);
|
|
|
|
updateBuffer();
|
|
update3DPosition();
|
|
}
|
|
|
|
|
|
bool LLAudioChannel::updateBuffer()
|
|
{
|
|
if (!mCurrentSourcep)
|
|
{
|
|
// This channel isn't associated with any source, nothing
|
|
// to be updated
|
|
return false;
|
|
}
|
|
|
|
// Initialize the channel's gain setting for this sound.
|
|
if(gAudiop)
|
|
{
|
|
setSecondaryGain(gAudiop->getSecondaryGain(mCurrentSourcep->getType()));
|
|
}
|
|
|
|
LLAudioBuffer *bufferp = mCurrentSourcep->getCurrentBuffer();
|
|
|
|
//This buffer is still in use, so mark it as such, and reset the use timer.
|
|
if (bufferp)
|
|
{
|
|
bufferp->mLastUseTimer.reset();
|
|
bufferp->mInUse = true;
|
|
}
|
|
|
|
//Return true if the buffer changed and is not null.
|
|
return bufferp != mCurrentBufferp && !!(mCurrentBufferp = bufferp);
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// LLAudioData implementation
|
|
//
|
|
|
|
|
|
LLAudioData::LLAudioData(const LLUUID &uuid) :
|
|
mID(uuid),
|
|
mBufferp(NULL),
|
|
mLoadState(STATE_LOAD_ERROR)
|
|
{
|
|
if (uuid.isNull())
|
|
{
|
|
// This is a null sound.
|
|
return;
|
|
}
|
|
|
|
if(gAudiop->hasDecodedFile(getID()))
|
|
mLoadState = STATE_LOAD_READY;
|
|
else if(gAssetStorage && gAssetStorage->hasLocalAsset(getID(), LLAssetType::AT_SOUND))
|
|
mLoadState = STATE_LOAD_REQ_DECODE;
|
|
else
|
|
mLoadState = STATE_LOAD_REQ_FETCH;
|
|
}
|
|
|
|
void LLAudioData::updateLoadState()
|
|
{
|
|
if(mLoadState == STATE_LOAD_REQ_DECODE && gAudioDecodeMgrp)
|
|
{
|
|
if( gAudioDecodeMgrp->addDecodeRequest(getID()) )
|
|
{
|
|
setLoadState(STATE_LOAD_DECODING);
|
|
LL_DEBUGS("AudioEngine") << "Decoding asset data for: " << getID() << LL_ENDL;
|
|
}
|
|
else
|
|
{
|
|
setLoadState(STATE_LOAD_ERROR);
|
|
}
|
|
}
|
|
else if(mLoadState == STATE_LOAD_REQ_FETCH && gAssetStorage && gAssetStorage->isUpstreamOK())
|
|
{
|
|
LL_DEBUGS("AudioEngine") << "Fetching asset data for: " << getID() << LL_ENDL;
|
|
setLoadState(STATE_LOAD_FETCHING);
|
|
|
|
gAssetStorage->getAssetData(getID(), LLAssetType::AT_SOUND, LLAudioEngine::assetCallback, NULL);
|
|
}
|
|
}
|
|
|
|
//return false when the audio file is corrupted.
|
|
bool LLAudioData::load()
|
|
{
|
|
// For now, just assume we're going to use one buffer per audiodata.
|
|
if (mBufferp)
|
|
{
|
|
// We already have this sound in a buffer, don't do anything.
|
|
LL_INFOS("AudioEngine") << "Already have a buffer for this sound, don't bother loading!" << LL_ENDL;
|
|
return true;
|
|
}
|
|
|
|
mBufferp = gAudiop->getFreeBuffer();
|
|
if (!mBufferp)
|
|
{
|
|
// No free buffers, abort.
|
|
LL_DEBUGS("AudioEngine") << "Not able to allocate a new audio buffer, aborting." << LL_ENDL;
|
|
return false;
|
|
}
|
|
|
|
std::string uuid_str;
|
|
std::string wav_path;
|
|
mID.toString(uuid_str);
|
|
wav_path= gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str) + ".dsf";
|
|
|
|
if (!mBufferp->loadWAV(wav_path))
|
|
{
|
|
// Hrm. Right now, let's unset the buffer, since it's empty.
|
|
gAudiop->cleanupBuffer(mBufferp);
|
|
mBufferp = NULL;
|
|
|
|
return false;
|
|
}
|
|
mBufferp->mAudioDatap = this;
|
|
return true;
|
|
}
|
|
|
|
// <edit>
|
|
std::map<LLUUID, LLSoundHistoryItem> gSoundHistory;
|
|
|
|
// static
|
|
void logSoundPlay(LLAudioSource* audio_source, LLUUID const& assetid)
|
|
{
|
|
LLSoundHistoryItem item;
|
|
item.mID = audio_source->getLogID();
|
|
item.mAudioSource = audio_source;
|
|
item.mPosition = audio_source->getPositionGlobal();
|
|
item.mType = audio_source->getType();
|
|
item.mAssetID = assetid;
|
|
item.mOwnerID = audio_source->getOwnerID();
|
|
item.mSourceID = audio_source->getSourceID();
|
|
item.mPlaying = true;
|
|
item.mTimeStarted = LLTimer::getElapsedSeconds();
|
|
item.mTimeStopped = F64_MAX;
|
|
item.mIsTrigger = audio_source->getIsTrigger();
|
|
item.mIsLooped = audio_source->isLoop();
|
|
|
|
item.mReviewed = false;
|
|
item.mReviewedCollision = false;
|
|
|
|
gSoundHistory[item.mID] = item;
|
|
}
|
|
|
|
//static
|
|
void logSoundStop(LLUUID const& id, bool destructed)
|
|
{
|
|
std::map<LLUUID, LLSoundHistoryItem>::iterator iter = gSoundHistory.find(id);
|
|
if(iter != gSoundHistory.end() && iter->second.mAudioSource)
|
|
{
|
|
iter->second.mPlaying = false;
|
|
iter->second.mTimeStopped = LLTimer::getElapsedSeconds();
|
|
if (destructed)
|
|
iter->second.mAudioSource = NULL;
|
|
pruneSoundLog();
|
|
}
|
|
}
|
|
|
|
//static
|
|
void pruneSoundLog()
|
|
{
|
|
if(++gSoundHistoryPruneCounter >= 64)
|
|
{
|
|
gSoundHistoryPruneCounter = 0;
|
|
while(gSoundHistory.size() > 256)
|
|
{
|
|
std::map<LLUUID, LLSoundHistoryItem>::iterator iter = gSoundHistory.begin();
|
|
std::map<LLUUID, LLSoundHistoryItem>::iterator end = gSoundHistory.end();
|
|
U64 lowest_time = (*iter).second.mTimeStopped;
|
|
LLUUID lowest_id = (*iter).first;
|
|
for( ; iter != end; ++iter)
|
|
{
|
|
if((*iter).second.mTimeStopped < lowest_time)
|
|
{
|
|
lowest_time = (*iter).second.mTimeStopped;
|
|
lowest_id = (*iter).first;
|
|
}
|
|
}
|
|
gSoundHistory.erase(lowest_id);
|
|
}
|
|
}
|
|
}
|
|
// </edit>
|