llaudioengine.cpp
上传用户:king477883
上传日期:2021-03-01
资源大小:9553k
文件大小:34k
- /**
- * @file audioengine.cpp
- * @brief implementation of LLAudioEngine class abstracting the Open
- * AL audio support
- *
- * $LicenseInfo:firstyear=2000&license=viewergpl$
- *
- * Copyright (c) 2000-2010, Linden Research, Inc.
- *
- * Second Life Viewer Source Code
- * The source code in this file ("Source Code") is provided by Linden Lab
- * to you under the terms of the GNU General Public License, version 2.0
- * ("GPL"), unless you have obtained a separate licensing agreement
- * ("Other License"), formally executed by you and Linden Lab. Terms of
- * the GPL can be found in doc/GPL-license.txt in this distribution, or
- * online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
- *
- * There are special exceptions to the terms and conditions of the GPL as
- * it is applied to this Source Code. View the full text of the exception
- * in the file doc/FLOSS-exception.txt in this software distribution, or
- * online at
- * http://secondlifegrid.net/programs/open_source/licensing/flossexception
- *
- * By copying, modifying or distributing this software, you acknowledge
- * that you have read and understood your obligations described above,
- * and agree to abide by those obligations.
- *
- * ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
- * WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
- * COMPLETENESS OR PERFORMANCE.
- * $/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"
- // necessary for grabbing sounds from sim (implemented in viewer)
- extern void request_sound(const LLUUID &sound_guid);
- LLAudioEngine* gAudiop = NULL;
- //
- // 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;
- mNextWindUpdate = 0.f;
- mStreamingAudioImpl = NULL;
- for (U32 i = 0; i < LLAudioEngine::AUDIO_TYPE_COUNT; i++)
- mSecondaryGain[i] = 1.0f;
- }
- bool LLAudioEngine::init(const S32 num_channels, void* userdata)
- {
- setDefaults();
- mNumChannels = num_channels;
- mUserData = userdata;
-
- allocateListener();
- // Initialize the decode manager
- gAudioDecodeMgrp = new LLAudioDecodeMgr;
- llinfos << "LLAudioEngine::init() AudioEngine successfully initialized" << llendl;
- 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::updateChannels()
- {
- S32 i;
- for (i = 0; i < MAX_CHANNELS; i++)
- {
- if (mChannels[i])
- {
- mChannels[i]->updateBuffer();
- mChannels[i]->update3DPosition();
- mChannels[i]->updateLoop();
- }
- }
- }
- 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.
- if (getMuted())
- {
- setInternalGain(0.f);
- }
- else
- {
- setInternalGain(getMasterGain());
- }
- S32 i;
- for (i = 0; i < MAX_BUFFERS; i++)
- {
- if (mBuffers[i])
- {
- mBuffers[i]->mInUse = false;
- }
- }
- F32 max_priority = -1.f;
- LLAudioSource *max_sourcep = NULL; // Maximum priority source without a channel
- source_map::iterator iter;
- for (iter = mAllSources.begin(); iter != mAllSources.end();)
- {
- LLAudioSource *sourcep = iter->second;
- // Update this source
- sourcep->update();
- sourcep->updatePriority();
- if (sourcep->isDone())
- {
- // The source is done playing, clean it up.
- delete sourcep;
- mAllSources.erase(iter++);
- continue;
- }
- if (!sourcep->getChannel() && sourcep->getCurrentBuffer())
- {
- // We could potentially play this sound if its priority is high enough.
- if (sourcep->getPriority() > max_priority)
- {
- max_priority = sourcep->getPriority();
- max_sourcep = sourcep;
- }
- }
- // Move on to the next source
- iter++;
- }
- // Now, do priority-based organization of audio sources.
- // All channels used, check priorities.
- // Find channel with lowest priority
- if (max_sourcep)
- {
- LLAudioChannel *channelp = getFreeChannel(max_priority);
- if (channelp)
- {
- //llinfos << "Replacing source in channel due to priority!" << llendl;
- max_sourcep->setChannel(channelp);
- channelp->setSource(max_sourcep);
- if (max_sourcep->isSyncSlave())
- {
- // A sync slave, it doesn't start playing until it's synced up with the master.
- // Flag this channel as waiting for sync, and return true.
- channelp->setWaiting(true);
- }
- else
- {
- channelp->setWaiting(false);
- channelp->play();
- }
- }
- }
-
- // 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();
- // Update queued sounds (switch to next queued data if the current has finished playing)
- for (iter = mAllSources.begin(); iter != mAllSources.end(); ++iter)
- {
- // 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 = iter->second;
- if (!sourcep->mQueuedDatap)
- {
- // Nothing queued, so we don't care.
- continue;
- }
- LLAudioChannel *channelp = sourcep->getChannel();
- if (!channelp)
- {
- // This sound isn't playing, so we just process move the queue
- sourcep->mCurrentDatap = sourcep->mQueuedDatap;
- sourcep->mQueuedDatap = NULL;
- // Reset the timer so the source doesn't die.
- sourcep->mAgeTimer.reset();
- // Make sure we have the buffer set up if we just decoded the data
- if (sourcep->mCurrentDatap)
- {
- updateBufferForData(sourcep->mCurrentDatap);
- }
- // Actually play the associated data.
- sourcep->setupChannel();
- channelp = sourcep->getChannel();
- if (channelp)
- {
- channelp->updateBuffer();
- sourcep->getChannel()->play();
- }
- continue;
- }
- else
- {
- // Check to see if the current sound is done playing, or looped.
- if (!channelp->isPlaying())
- {
- sourcep->mCurrentDatap = sourcep->mQueuedDatap;
- sourcep->mQueuedDatap = NULL;
- // Reset the timer so the source doesn't die.
- sourcep->mAgeTimer.reset();
- // Make sure we have the buffer set up if we just decoded the data
- if (sourcep->mCurrentDatap)
- {
- updateBufferForData(sourcep->mCurrentDatap);
- }
- // Actually play the associated data.
- sourcep->setupChannel();
- channelp->updateBuffer();
- sourcep->getChannel()->play();
- }
- else if (sourcep->isLoop())
- {
- // It's a loop, we need to check and see if we're done with it.
- if (channelp->mLoopedThisFrame)
- {
- sourcep->mCurrentDatap = sourcep->mQueuedDatap;
- sourcep->mQueuedDatap = NULL;
- // Actually, should do a time sync so if we're a loop master/slave
- // we don't drift away.
- sourcep->setupChannel();
- sourcep->getChannel()->play();
- }
- }
- }
- }
- // Lame, update the channels AGAIN.
- // Update the channels to sync up with any changes that the source made,
- // such as changing what sound was playing.
- updateChannels();
-
- // Hack! For now, just use a global sync master;
- LLAudioSource *sync_masterp = NULL;
- LLAudioChannel *master_channelp = NULL;
- F32 max_sm_priority = -1.f;
- for (iter = mAllSources.begin(); iter != mAllSources.end(); ++iter)
- {
- LLAudioSource *sourcep = iter->second;
- if (sourcep->isSyncMaster())
- {
- if (sourcep->getPriority() > max_sm_priority)
- {
- sync_masterp = sourcep;
- master_channelp = sync_masterp->getChannel();
- max_sm_priority = sourcep->getPriority();
- }
- }
- }
- if (master_channelp && master_channelp->mLoopedThisFrame)
- {
- // Synchronize loop slaves with their masters
- // Update queued sounds (switch to next queued data if the current has finished playing)
- for (iter = mAllSources.begin(); iter != mAllSources.end(); ++iter)
- {
- LLAudioSource *sourcep = iter->second;
- if (!sourcep->isSyncSlave())
- {
- // Not a loop slave, we don't need to do anything
- continue;
- }
- LLAudioChannel *channelp = sourcep->getChannel();
- if (!channelp)
- {
- // Not playing, don't need to bother.
- continue;
- }
- if (!channelp->isPlaying())
- {
- // Now we need to check if our loop master has just looped, and
- // start playback if that's the case.
- if (sync_masterp->getChannel())
- {
- channelp->playSynced(master_channelp);
- channelp->setWaiting(false);
- }
- }
- }
- }
- // 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)
- {
- //llinfos << "Flushing unused buffer!" << llendl;
- 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();
- }
- bool LLAudioEngine::updateBufferForData(LLAudioData *adp, const LLUUID &audio_uuid)
- {
- if (!adp)
- {
- return false;
- }
- // 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->getBuffer())
- {
- if (adp->hasDecodedData())
- {
- adp->load();
- }
- else if (adp->hasLocalData())
- {
- if (audio_uuid.notNull())
- {
- gAudioDecodeMgrp->addDecodeRequest(audio_uuid);
- }
- }
- else
- {
- return false;
- }
- }
- return true;
- }
- void LLAudioEngine::enableWind(bool enable)
- {
- if (enable && (!mEnableWind))
- {
- initWind();
- mEnableWind = enable;
- }
- else if (mEnableWind && (!enable))
- {
- mEnableWind = enable;
- cleanupWind();
- }
- }
- LLAudioBuffer * LLAudioEngine::getFreeBuffer()
- {
- S32 i;
- for (i = 0; i < MAX_BUFFERS; i++)
- {
- if (!mBuffers[i])
- {
- mBuffers[i] = createBuffer();
- return mBuffers[i];
- }
- }
- // 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;
- }
- }
- }
- }
- if (buffer_id >= 0)
- {
- llinfos << "Taking over unused buffer " << buffer_id << llendl;
- //llinfos << "Flushing unused buffer!" << llendl;
- mBuffers[buffer_id]->mAudioDatap->mBufferp = NULL;
- delete mBuffers[buffer_id];
- mBuffers[buffer_id] = createBuffer();
- return mBuffers[buffer_id];
- }
- 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]->isPlaying() && !mChannels[i]->isWaiting())
- {
- mChannels[i]->cleanup();
- if (mChannels[i]->getSource())
- {
- mChannels[i]->getSource()->setChannel(NULL);
- }
- 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;
- }
- // Flush the minimum priority channel, and return it.
- min_channelp->cleanup();
- min_channelp->getSource()->setChannel(NULL);
- 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)
- {
- 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;
- }
- // 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.
- //llwarns << "Used internal preload for non-local sound" << llendl;
- return false;
- }
- bool LLAudioEngine::isWindEnabled()
- {
- return mEnableWind;
- }
- void LLAudioEngine::setMuted(bool muted)
- {
- mMuted = muted;
- enableWind(!mMuted);
- }
- void LLAudioEngine::setMasterGain(const F32 gain)
- {
- mMasterGain = gain;
- setInternalGain(gain);
- }
- 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)
- {
- // Create a new source (since this can't be associated with an existing source.
- //llinfos << "Localized: " << audio_uuid << llendl;
- if (mMuted)
- {
- return;
- }
- LLUUID source_id;
- source_id.generate();
- LLAudioSource *asp = new LLAudioSource(source_id, owner_id, gain, type);
- 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)
- {
- mListenerp->setPosition(aVec);
- }
- LLVector3 LLAudioEngine::getListenerPos()
- {
- if (mListenerp)
- {
- return(mListenerp->getPosition());
- }
- else
- {
- return(LLVector3::zero);
- }
- }
- void LLAudioEngine::setListenerVelocity(LLVector3 aVec)
- {
- mListenerp->setVelocity(aVec);
- }
- void LLAudioEngine::translateListener(LLVector3 aVec)
- {
- mListenerp->translate(aVec);
- }
- void LLAudioEngine::orientListener(LLVector3 up, LLVector3 at)
- {
- mListenerp->orient(up, at);
- }
- void LLAudioEngine::setListener(LLVector3 pos, LLVector3 vel, LLVector3 up, LLVector3 at)
- {
- 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()
- {
- 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::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())
- {
- llwarns << "Cleaning up unknown audio source!" << llendl;
- 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()
- {
- //llinfos << "LLAudioEngine::startNextTransfer()" << llendl;
- if (mCurrentTransfer.notNull() || getMuted())
- {
- //llinfos << "Transfer in progress, aborting" << llendl;
- 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.notNull())
- {
- llinfos << "Getting asset data for: " << asset_id << llendl;
- gAudiop->mCurrentTransfer = asset_id;
- gAudiop->mCurrentTransferTimer.reset();
- gAssetStorage->getAssetData(asset_id, LLAssetType::AT_SOUND,
- assetCallback, NULL);
- }
- else
- {
- //llinfos << "No pending transfers?" << llendl;
- }
- }
- // static
- void LLAudioEngine::assetCallback(LLVFS *vfs, const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status)
- {
- if (result_code)
- {
- llinfos << "Boom, error in audio file transfer: " << LLAssetStorage::getErrorString( result_code ) << " (" << result_code << ")" << llendl;
- // Need to mark data as bad to avoid constant rerequests.
- LLAudioData *adp = gAudiop->getAudioData(uuid);
- if (adp)
- {
- adp->setHasValidData(false);
- adp->setHasLocalData(false);
- adp->setHasDecodedData(false);
- }
- }
- else
- {
- LLAudioData *adp = gAudiop->getAudioData(uuid);
- if (!adp)
- {
- // Should never happen
- llwarns << "Got asset callback without audio data for " << uuid << llendl;
- }
- else
- {
- 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)
- : mID(id),
- mOwnerID(owner_id),
- mPriority(0.f),
- mGain(gain),
- mType(type),
- mAmbient(false),
- mLoop(false),
- mSyncMaster(false),
- mSyncSlave(false),
- mQueueSounds(false),
- mPlayedOnce(false),
- mChannelp(NULL),
- mCurrentDatap(NULL),
- mQueuedDatap(NULL)
- {
- }
- LLAudioSource::~LLAudioSource()
- {
- if (mChannelp)
- {
- // Stop playback of this sound
- mChannelp->setSource(NULL);
- mChannelp = NULL;
- }
- }
- void LLAudioSource::setChannel(LLAudioChannel *channelp)
- {
- if (channelp == mChannelp)
- {
- return;
- }
- mChannelp = channelp;
- }
- void LLAudioSource::update()
- {
- if (!getCurrentBuffer())
- {
- if (getCurrentData())
- {
- // Hack - try and load the sound. Will do this as a callback
- // on decode later.
- if (getCurrentData()->load())
- {
- play(getCurrentData()->getID());
- }
- }
- }
- }
- void LLAudioSource::updatePriority()
- {
- if (isAmbient())
- {
- mPriority = 1.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::setupChannel()
- {
- LLAudioData *adp = getCurrentData();
- if (!adp->getBuffer())
- {
- // We're not ready to play back the sound yet, so don't try and allocate a channel for it.
- //llwarns << "Aborting, no buffer" << llendl;
- return false;
- }
- if (!mChannelp)
- {
- // Update the priority, in case we need to push out another channel.
- updatePriority();
- setChannel(gAudiop->getFreeChannel(getPriority()));
- }
- if (!mChannelp)
- {
- // Ugh, we don't have any free channels.
- // Now we have to reprioritize.
- // For now, just don't play the sound.
- //llwarns << "Aborting, no free channels" << llendl;
- return false;
- }
- mChannelp->setSource(this);
- return true;
- }
- bool LLAudioSource::play(const LLUUID &audio_uuid)
- {
- if (audio_uuid.isNull())
- {
- if (getChannel())
- {
- getChannel()->setSource(NULL);
- setChannel(NULL);
- addAudioData(NULL, true);
- }
- }
- // Reset our age timeout if someone attempts to play the source.
- mAgeTimer.reset();
- LLAudioData *adp = gAudiop->getAudioData(audio_uuid);
- bool has_buffer = gAudiop->updateBufferForData(adp, audio_uuid);
- addAudioData(adp);
- if (!has_buffer)
- {
- // Don't bother trying to set up a channel or anything, we don't have an audio buffer.
- return false;
- }
- if (!setupChannel())
- {
- return false;
- }
- if (isSyncSlave())
- {
- // A sync slave, it doesn't start playing until it's synced up with the master.
- // Flag this channel as waiting for sync, and return true.
- getChannel()->setWaiting(true);
- return true;
- }
- getChannel()->play();
- return true;
- }
- bool LLAudioSource::isDone()
- {
- const F32 MAX_AGE = 60.f;
- const F32 MAX_UNPLAYED_AGE = 15.f;
- if (isLoop())
- {
- // Looped sources never die on their own.
- return false;
- }
- if (hasPendingPreloads())
- {
- return false;
- }
- if (mQueuedDatap)
- {
- // Don't kill this sound if we've got something queued up to play.
- return false;
- }
- F32 elapsed = mAgeTimer.getElapsedTimeF32();
- // This is a single-play source
- if (!mChannelp)
- {
- if ((elapsed > MAX_UNPLAYED_AGE) || mPlayedOnce)
- {
- // We don't have a channel assigned, and it's been
- // over 5 seconds since we tried to play it. Don't bother.
- //llinfos << "No channel assigned, source is done" << llendl;
- return true;
- }
- else
- {
- return false;
- }
- }
- if (mChannelp->isPlaying())
- {
- if (elapsed > MAX_AGE)
- {
- // Arbitarily cut off non-looped sounds when they're old.
- return true;
- }
- else
- {
- // Sound is still playing and we haven't timed out, don't kill it.
- return false;
- }
- }
- if ((elapsed > MAX_UNPLAYED_AGE) || mPlayedOnce)
- {
- // The sound isn't playing back after 5 seconds or we're already done playing it, kill it.
- return true;
- }
- return false;
- }
- void LLAudioSource::addAudioData(LLAudioData *adp, const bool set_current)
- {
- // Only handle a single piece of audio data associated with a source right now,
- // until I implement prefetch.
- if (set_current)
- {
- if (!mCurrentDatap)
- {
- mCurrentDatap = adp;
- if (mChannelp)
- {
- mChannelp->updateBuffer();
- mChannelp->play();
- }
- // Make sure the audio engine knows that we want to request this sound.
- gAudiop->startNextTransfer();
- return;
- }
- else if (mQueueSounds)
- {
- // If we have current data, and we're queuing, put
- // the object onto the queue.
- if (mQueuedDatap)
- {
- // We only queue one sound at a time, and it's a FIFO.
- // Don't put it onto the queue.
- return;
- }
- if (adp == mCurrentDatap && isLoop())
- {
- // No point in queueing the same sound if
- // we're looping.
- return;
- }
- mQueuedDatap = adp;
- // Make sure the audio engine knows that we want to request this sound.
- gAudiop->startNextTransfer();
- }
- else
- {
- if (mCurrentDatap != adp)
- {
- // Right now, if we're currently playing this sound in a channel, we
- // update the buffer that the channel's associated with
- // and play it. This may not be the correct behavior.
- mCurrentDatap = adp;
- if (mChannelp)
- {
- mChannelp->updateBuffer();
- mChannelp->play();
- }
- // Make sure the audio engine knows that we want to request this sound.
- gAudiop->startNextTransfer();
- }
- }
- }
- else
- {
- // Add it to the preload list.
- mPreloadMap[adp->getID()] = adp;
- 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->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),
- mWaiting(false),
- mSecondaryGain(1.0f)
- {
- }
- LLAudioChannel::~LLAudioChannel()
- {
- // Need to disconnect any sources which are using this channel.
- //llinfos << "Cleaning up audio channel" << llendl;
- if (mCurrentSourcep)
- {
- mCurrentSourcep->setChannel(NULL);
- }
- mCurrentBufferp = NULL;
- }
- void LLAudioChannel::setSource(LLAudioSource *sourcep)
- {
- //llinfos << this << ": setSource(" << sourcep << ")" << llendl;
- if (!sourcep)
- {
- // Clearing the source for this channel, don't need to do anything.
- //llinfos << "Clearing source for channel" << llendl;
- cleanup();
- mCurrentSourcep = NULL;
- mWaiting = false;
- return;
- }
- if (sourcep == mCurrentSourcep)
- {
- // Don't reallocate the channel, this will make FMOD goofy.
- //llinfos << "Calling setSource with same source!" << llendl;
- }
- mCurrentSourcep = sourcep;
- 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();
- if (bufferp == mCurrentBufferp)
- {
- if (bufferp)
- {
- // The source hasn't changed what buffer it's playing
- bufferp->mLastUseTimer.reset();
- bufferp->mInUse = true;
- }
- return false;
- }
- //
- // The source changed what buffer it's playing. We need to clean up
- // the existing channel
- //
- cleanup();
- mCurrentBufferp = bufferp;
- if (bufferp)
- {
- bufferp->mLastUseTimer.reset();
- bufferp->mInUse = true;
- }
- if (!mCurrentBufferp)
- {
- // There's no new buffer to be played, so we just abort.
- return false;
- }
- return true;
- }
- //
- // LLAudioData implementation
- //
- LLAudioData::LLAudioData(const LLUUID &uuid) :
- mID(uuid),
- mBufferp(NULL),
- mHasLocalData(false),
- mHasDecodedData(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.
- mHasLocalData = true;
- mHasDecodedData = true;
- }
- else if (gAssetStorage && gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
- {
- mHasLocalData = true;
- }
- }
- 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.
- llinfos << "Already have a buffer for this sound, don't bother loading!" << llendl;
- return true;
- }
-
- mBufferp = gAudiop->getFreeBuffer();
- if (!mBufferp)
- {
- // No free buffers, abort.
- llinfos << "Not able to allocate a new audio buffer, aborting." << llendl;
- 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;
- }