/** * @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 #include // 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; 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; } } // 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(); } typedef std::pair audio_source_t; struct SourcePriorityComparator { bool operator() (const audio_source_t& lhs, const audio_source_t& 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()); } }; 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 //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; for (source_map::iterator iter = mAllSources.begin(); iter != mAllSources.end(); ++iter) { 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; } 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()) { sync_masterp = sourcep; } //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 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()) { sync_slavep = sourcep; } } else { continue; } } else 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())); } // 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(); //Syncmaster must have priority greater than or equal to priority of highest priority syncslave. if(sync_masterp && !sync_masterp->getChannel()) { //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())); } // Dispatch all soundsources. bool syncmaster_started = false; 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; queue.pop(); if (sourcep->isSyncSlave()) { //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; } 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) { syncmaster_started = true; } channelp->setSource(sourcep); //setSource calls updateBuffer and update3DPosition, and resets the source mAgeTimer } 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); // Call this every frame, just in case we somehow // missed picking it up in all the places that can add // or request new data. 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_priority > priority || !min_channelp) { // 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; gAudiop->getAudioData(uuid); // We don't care about the return value, this is just to make sure // that we have an entry, which will mean that the audio engine knows about this if (gAudioDecodeMgrp->addDecodeRequest(uuid)) { // 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, // //const S32 type, const LLVector3d &pos_global) const S32 type, const LLVector3d &pos_global, const LLUUID source_object) // { 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(); // //LLAudioSource *asp = new LLAudioSource(source_id, owner_id, gain, type); LLAudioSource *asp = new LLAudioSource(source_id, owner_id, gain, type, source_object, true); // 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; } } bool LLAudioEngine::hasLocalFile(const LLUUID &uuid) { // See if it's in the VFS. return gVFS->getExists(uuid, LLAssetType::AT_SOUND); } void LLAudioEngine::startNextTransfer() { //LL_INFOS("AudioEngine") << "LLAudioEngine::startNextTransfer()" << LL_ENDL; if (!gAssetStorage->isUpstreamOK() || mCurrentTransfer.notNull() || getMuted()) { //LL_INFOS("AudioEngine") << "Transfer in progress, aborting" << LL_ENDL; return; } // Get the ID for the next asset that we want to transfer. // Pick one in the following order: LLUUID asset_id; S32 i; LLAudioSource *asp = NULL; LLAudioData *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->hasLocalData() && adp->hasValidData()) { asset_id = adp->getID(); max_pri = asp->getPriority(); } } // Check all channels for currently queued sounds. if (asset_id.isNull()) { 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->hasLocalData() && adp->hasValidData()) { asset_id = adp->getID(); max_pri = asp->getPriority(); } } } // Check all live channels for other sounds (preloads). if (asset_id.isNull()) { 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->hasLocalData() && adp->hasValidData()) { asset_id = adp->getID(); max_pri = asp->getPriority(); } } } } // Check all sources if (asset_id.isNull()) { 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->hasLocalData() && adp->hasValidData()) { asset_id = adp->getID(); max_pri = asp->getPriority(); continue; } adp = asp->getQueuedData(); if (adp && !adp->hasLocalData() && adp->hasValidData()) { asset_id = adp->getID(); max_pri = asp->getPriority(); continue; } for (data_iter = asp->mPreloadMap.begin(); data_iter != asp->mPreloadMap.end(); data_iter++) { LLAudioData *adp = data_iter->second; if (!adp) { continue; } if (!adp->hasLocalData() && adp->hasValidData()) { asset_id = adp->getID(); max_pri = asp->getPriority(); break; } } } } if (asset_id.isNull() && !mPreloadSystemList.empty()) { asset_id = mPreloadSystemList.front(); mPreloadSystemList.pop_front(); } else if(asset_id.notNull()) { std::list::iterator it = std::find(mPreloadSystemList.begin(),mPreloadSystemList.end(),asset_id); if(it != mPreloadSystemList.end()) mPreloadSystemList.erase(it); } if (asset_id.notNull()) { LL_DEBUGS("AudioEngine") << "Getting asset data for: " << asset_id << LL_ENDL; gAudiop->mCurrentTransfer = asset_id; gAudiop->mCurrentTransferTimer.reset(); gAssetStorage->getAssetData(asset_id, LLAssetType::AT_SOUND, assetCallback, NULL); } 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; 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. LLAudioData *adp = gAudiop->getAudioData(uuid); if (adp) { // Make sure everything is cleared adp->setHasValidData(false); adp->setHasLocalData(false); adp->setHasDecodedData(false); adp->setHasCompletedDecode(true); } } else { LLAudioData *adp = gAudiop->getAudioData(uuid); 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->setHasValidData(true); adp->setHasLocalData(true); gAudioDecodeMgrp->addDecodeRequest(uuid); } } gAudiop->mCurrentTransfer = LLUUID::null; gAudiop->startNextTransfer(); } // // LLAudioSource implementation // // //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) // : 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), // mSourceID(source_id), mIsTrigger(isTrigger), // mChannelp(NULL), mCurrentDatap(NULL), mQueuedDatap(NULL) { // mLogID.generate(); // } LLAudioSource::~LLAudioSource() { // // Record destruction of LLAudioSource object. if(mType != LLAudioEngine::AUDIO_TYPE_UI) logSoundStop(mLogID, true); // 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(); // if (!channelp) { if(mType != LLAudioEngine::AUDIO_TYPE_UI) logSoundStop(mLogID, false); } // 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(); gAudiop->startNextTransfer(); } LLAudioData *adp = getCurrentData(); if (adp && !adp->getBuffer()) { // 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. if (adp->hasDecodedData()) { if(!adp->load() && adp->hasCompletedDecode()) { LL_WARNS("AudioEngine") << "Marking LLAudioSource corrupted for " << adp->getID() << LL_ENDL; mCorrupted = true ; } } else if (adp->hasLocalData() && adp->hasValidData()) { if (adp->getID().notNull()) { gAudioDecodeMgrp->addDecodeRequest(adp->getID()); } } } } void LLAudioSource::updatePriority() { if (isAmbient()) { mPriority = 1.f; } else if (isMuted()) { mPriority = 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()); mPriority = 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; } // if(mType != LLAudioEngine::AUDIO_TYPE_UI) //&& mSourceID.notNull()) logSoundPlay(this, audio_uuid); // // 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; // Make sure the audio engine knows that we want to request this sound. gAudiop->startNextTransfer(); 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->hasDecodedData() && adp->hasValidData()) 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); gAudiop->startNextTransfer(); } } 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->hasDecodedData() && adp->hasValidData()) { // 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), mHasLocalData(false), mHasDecodedData(false), mHasCompletedDecode(false), mHasValidData(true) { if (uuid.isNull()) { // This is a null sound. return; } if (gAudiop && gAudiop->hasDecodedFile(uuid)) { // Already have a decoded version, don't need to decode it. setHasLocalData(true); setHasDecodedData(true); setHasCompletedDecode(true); } else if (gAssetStorage && gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND)) { setHasLocalData(true); } } //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; } // std::map 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::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::iterator iter = gSoundHistory.begin(); std::map::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); } } } //