diff options
Diffstat (limited to 'indra/llaudio')
-rw-r--r-- | indra/llaudio/CMakeLists.txt | 15 | ||||
-rwxr-xr-x | indra/llaudio/llaudiodecodemgr.cpp | 1602 | ||||
-rw-r--r-- | indra/llaudio/llaudiodecodemgr.h | 108 | ||||
-rwxr-xr-x | indra/llaudio/llaudioengine.h | 972 | ||||
-rw-r--r-- | indra/llaudio/llaudioengine_fmodstudio.cpp | 756 | ||||
-rw-r--r-- | indra/llaudio/llaudioengine_fmodstudio.h | 131 | ||||
-rw-r--r-- | indra/llaudio/llaudioengine_openal.cpp | 1111 | ||||
-rw-r--r-- | indra/llaudio/llaudioengine_openal.h | 216 | ||||
-rw-r--r-- | indra/llaudio/lllistener_fmodstudio.cpp | 136 | ||||
-rw-r--r-- | indra/llaudio/lllistener_fmodstudio.h | 65 | ||||
-rw-r--r-- | indra/llaudio/lllistener_openal.h | 117 | ||||
-rw-r--r-- | indra/llaudio/llstreamingaudio_fmodstudio.cpp | 481 | ||||
-rw-r--r-- | indra/llaudio/llstreamingaudio_fmodstudio.h | 76 | ||||
-rw-r--r-- | indra/llaudio/llvorbisencode.cpp | 1012 |
14 files changed, 2574 insertions, 4224 deletions
diff --git a/indra/llaudio/CMakeLists.txt b/indra/llaudio/CMakeLists.txt index 4f469b9bb5..9278d3c488 100644 --- a/indra/llaudio/CMakeLists.txt +++ b/indra/llaudio/CMakeLists.txt @@ -4,7 +4,6 @@ project(llaudio) include(00-Common) include(LLAudio) -include(FMODSTUDIO) include(OPENAL) include(LLCommon) @@ -25,20 +24,6 @@ set(llaudio_HEADER_FILES llwindgen.h ) -if (TARGET ll::fmodstudio) - list(APPEND llaudio_SOURCE_FILES - llaudioengine_fmodstudio.cpp - lllistener_fmodstudio.cpp - llstreamingaudio_fmodstudio.cpp - ) - - list(APPEND llaudio_HEADER_FILES - llaudioengine_fmodstudio.h - lllistener_fmodstudio.h - llstreamingaudio_fmodstudio.h - ) -endif () - if (TARGET ll::openal) list(APPEND llaudio_SOURCE_FILES llaudioengine_openal.cpp diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index ba4939f595..602d360f1c 100755 --- a/indra/llaudio/llaudiodecodemgr.cpp +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -1,801 +1,801 @@ -/** - * @file llaudiodecodemgr.cpp - * - * $LicenseInfo:firstyear=2003&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 "llaudiodecodemgr.h" - -#include "llaudioengine.h" -#include "lllfsthread.h" -#include "llfilesystem.h" -#include "llstring.h" -#include "lldir.h" -#include "llendianswizzle.h" -#include "llassetstorage.h" -#include "llrefcount.h" -#include "threadpool.h" -#include "workqueue.h" - -#include "llvorbisencode.h" - -#include "vorbis/codec.h" -#include "vorbis/vorbisfile.h" -#include <iterator> -#include <deque> - -extern LLAudioEngine *gAudiop; - -static const S32 WAV_HEADER_SIZE = 44; - - -////////////////////////////////////////////////////////////////////////////// - - -class LLVorbisDecodeState : public LLThreadSafeRefCount -{ -public: - class WriteResponder : public LLLFSThread::Responder - { - public: - WriteResponder(LLVorbisDecodeState* decoder) : mDecoder(decoder) {} - ~WriteResponder() {} - void completed(S32 bytes) - { - mDecoder->ioComplete(bytes); - } - LLPointer<LLVorbisDecodeState> mDecoder; - }; - - LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename); - - BOOL initDecode(); - BOOL decodeSection(); // Return TRUE if done. - BOOL finishDecode(); - - void flushBadFile(); - - void ioComplete(S32 bytes) { mBytesRead = bytes; } - BOOL isValid() const { return mValid; } - BOOL isDone() const { return mDone; } - const LLUUID &getUUID() const { return mUUID; } - -protected: - virtual ~LLVorbisDecodeState(); - - BOOL mValid; - BOOL mDone; - LLAtomicS32 mBytesRead; - LLUUID mUUID; - - std::vector<U8> mWAVBuffer; - std::string mOutFilename; - LLLFSThread::handle_t mFileHandle; - - LLFileSystem *mInFilep; - OggVorbis_File mVF; - S32 mCurrentSection; -}; - -size_t cache_read(void *ptr, size_t size, size_t nmemb, void *datasource) -{ - LLFileSystem *file = (LLFileSystem *)datasource; - - if (file->read((U8*)ptr, (S32)(size * nmemb))) /*Flawfinder: ignore*/ - { - S32 read = file->getLastBytesRead(); - return read / size; /*Flawfinder: ignore*/ - } - else - { - return 0; - } -} - -S32 cache_seek(void *datasource, ogg_int64_t offset, S32 whence) -{ - LLFileSystem *file = (LLFileSystem *)datasource; - - // cache has 31-bit files - if (offset > S32_MAX) - { - return -1; - } - - S32 origin; - switch (whence) { - case SEEK_SET: - origin = 0; - break; - case SEEK_END: - origin = file->getSize(); - break; - case SEEK_CUR: - origin = -1; - break; - default: - LL_ERRS("AudioEngine") << "Invalid whence argument to cache_seek" << LL_ENDL; - return -1; - } - - if (file->seek((S32)offset, origin)) - { - return 0; - } - else - { - return -1; - } -} - -S32 cache_close (void *datasource) -{ - LLFileSystem *file = (LLFileSystem *)datasource; - delete file; - return 0; -} - -long cache_tell (void *datasource) -{ - LLFileSystem *file = (LLFileSystem *)datasource; - return file->tell(); -} - -LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename) -{ - mDone = FALSE; - mValid = FALSE; - mBytesRead = -1; - mUUID = uuid; - mInFilep = NULL; - mCurrentSection = 0; - mOutFilename = out_filename; - mFileHandle = LLLFSThread::nullHandle(); - - // No default value for mVF, it's an ogg structure? - // Hey, let's zero it anyway, for predictability. - memset(&mVF, 0, sizeof(mVF)); -} - -LLVorbisDecodeState::~LLVorbisDecodeState() -{ - if (!mDone) - { - delete mInFilep; - mInFilep = NULL; - } -} - - -BOOL LLVorbisDecodeState::initDecode() -{ - ov_callbacks cache_callbacks; - cache_callbacks.read_func = cache_read; - cache_callbacks.seek_func = cache_seek; - cache_callbacks.close_func = cache_close; - cache_callbacks.tell_func = cache_tell; - - LL_DEBUGS("AudioEngine") << "Initing decode from vfile: " << mUUID << LL_ENDL; - - mInFilep = new LLFileSystem(mUUID, LLAssetType::AT_SOUND); - if (!mInFilep || !mInFilep->getSize()) - { - LL_WARNS("AudioEngine") << "unable to open vorbis source vfile for reading" << LL_ENDL; - delete mInFilep; - mInFilep = NULL; - return FALSE; - } - - S32 r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, cache_callbacks); - if(r < 0) - { - LL_WARNS("AudioEngine") << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << LL_ENDL; - return(FALSE); - } - - S32 sample_count = (S32)ov_pcm_total(&mVF, -1); - size_t size_guess = (size_t)sample_count; - vorbis_info* vi = ov_info(&mVF, -1); - size_guess *= (vi? vi->channels : 1); - size_guess *= 2; - size_guess += 2048; - - bool abort_decode = false; - - if (vi) - { - if( vi->channels < 1 || vi->channels > LLVORBIS_CLIP_MAX_CHANNELS ) - { - abort_decode = true; - LL_WARNS("AudioEngine") << "Bad channel count: " << vi->channels << LL_ENDL; - } - } - else // !vi - { - abort_decode = true; - LL_WARNS("AudioEngine") << "No default bitstream found" << LL_ENDL; - } - - if( (size_t)sample_count > LLVORBIS_CLIP_REJECT_SAMPLES || - (size_t)sample_count <= 0) - { - abort_decode = true; - LL_WARNS("AudioEngine") << "Illegal sample count: " << sample_count << LL_ENDL; - } - - if( size_guess > LLVORBIS_CLIP_REJECT_SIZE ) - { - abort_decode = true; - LL_WARNS("AudioEngine") << "Illegal sample size: " << size_guess << LL_ENDL; - } - - if( abort_decode ) - { - LL_WARNS("AudioEngine") << "Canceling initDecode. Bad asset: " << mUUID << LL_ENDL; - vorbis_comment* comment = ov_comment(&mVF,-1); - if (comment && comment->vendor) - { - LL_WARNS("AudioEngine") << "Bad asset encoded by: " << comment->vendor << LL_ENDL; - } - delete mInFilep; - mInFilep = NULL; - return FALSE; - } - - try - { - mWAVBuffer.reserve(size_guess); - mWAVBuffer.resize(WAV_HEADER_SIZE); - } - catch (std::bad_alloc&) - { - LL_WARNS("AudioEngine") << "Out of memory when trying to alloc buffer: " << size_guess << LL_ENDL; - delete mInFilep; - mInFilep = NULL; - return FALSE; - } - - { - // write the .wav format header - //"RIFF" - mWAVBuffer[0] = 0x52; - mWAVBuffer[1] = 0x49; - mWAVBuffer[2] = 0x46; - mWAVBuffer[3] = 0x46; - - // length = datalen + 36 (to be filled in later) - mWAVBuffer[4] = 0x00; - mWAVBuffer[5] = 0x00; - mWAVBuffer[6] = 0x00; - mWAVBuffer[7] = 0x00; - - //"WAVE" - mWAVBuffer[8] = 0x57; - mWAVBuffer[9] = 0x41; - mWAVBuffer[10] = 0x56; - mWAVBuffer[11] = 0x45; - - // "fmt " - mWAVBuffer[12] = 0x66; - mWAVBuffer[13] = 0x6D; - mWAVBuffer[14] = 0x74; - mWAVBuffer[15] = 0x20; - - // chunk size = 16 - mWAVBuffer[16] = 0x10; - mWAVBuffer[17] = 0x00; - mWAVBuffer[18] = 0x00; - mWAVBuffer[19] = 0x00; - - // format (1 = PCM) - mWAVBuffer[20] = 0x01; - mWAVBuffer[21] = 0x00; - - // number of channels - mWAVBuffer[22] = 0x01; - mWAVBuffer[23] = 0x00; - - // samples per second - mWAVBuffer[24] = 0x44; - mWAVBuffer[25] = 0xAC; - mWAVBuffer[26] = 0x00; - mWAVBuffer[27] = 0x00; - - // average bytes per second - mWAVBuffer[28] = 0x88; - mWAVBuffer[29] = 0x58; - mWAVBuffer[30] = 0x01; - mWAVBuffer[31] = 0x00; - - // bytes to output at a single time - mWAVBuffer[32] = 0x02; - mWAVBuffer[33] = 0x00; - - // 16 bits per sample - mWAVBuffer[34] = 0x10; - mWAVBuffer[35] = 0x00; - - // "data" - mWAVBuffer[36] = 0x64; - mWAVBuffer[37] = 0x61; - mWAVBuffer[38] = 0x74; - mWAVBuffer[39] = 0x61; - - // these are the length of the data chunk, to be filled in later - mWAVBuffer[40] = 0x00; - mWAVBuffer[41] = 0x00; - mWAVBuffer[42] = 0x00; - mWAVBuffer[43] = 0x00; - } - - //{ - //char **ptr=ov_comment(&mVF,-1)->user_comments; -// vorbis_info *vi=ov_info(&vf,-1); - //while(*ptr){ - // fprintf(stderr,"%s\n",*ptr); - // ++ptr; - //} -// fprintf(stderr,"\nBitstream is %d channel, %ldHz\n",vi->channels,vi->rate); -// fprintf(stderr,"\nDecoded length: %ld samples\n", (long)ov_pcm_total(&vf,-1)); -// fprintf(stderr,"Encoded by: %s\n\n",ov_comment(&vf,-1)->vendor); - //} - return TRUE; -} - -BOOL LLVorbisDecodeState::decodeSection() -{ - if (!mInFilep) - { - LL_WARNS("AudioEngine") << "No cache file to decode in vorbis!" << LL_ENDL; - return TRUE; - } - if (mDone) - { -// LL_WARNS("AudioEngine") << "Already done with decode, aborting!" << LL_ENDL; - return TRUE; - } - char pcmout[4096]; /*Flawfinder: ignore*/ - - BOOL eof = FALSE; - long ret=ov_read(&mVF, pcmout, sizeof(pcmout), 0, 2, 1, &mCurrentSection); - if (ret == 0) - { - /* EOF */ - eof = TRUE; - mDone = TRUE; - mValid = TRUE; -// LL_INFOS("AudioEngine") << "Vorbis EOF" << LL_ENDL; - } - else if (ret < 0) - { - /* error in the stream. Not a problem, just reporting it in - case we (the app) cares. In this case, we don't. */ - - LL_WARNS("AudioEngine") << "BAD vorbis decode in decodeSection." << LL_ENDL; - - mValid = FALSE; - mDone = TRUE; - // We're done, return TRUE. - return TRUE; - } - else - { -// LL_INFOS("AudioEngine") << "Vorbis read " << ret << "bytes" << LL_ENDL; - /* we don't bother dealing with sample rate changes, etc, but. - you'll have to*/ - std::copy(pcmout, pcmout+ret, std::back_inserter(mWAVBuffer)); - } - return eof; -} - -BOOL LLVorbisDecodeState::finishDecode() -{ - if (!isValid()) - { - LL_WARNS("AudioEngine") << "Bogus vorbis decode state for " << getUUID() << ", aborting!" << LL_ENDL; - return TRUE; // We've finished - } - - if (mFileHandle == LLLFSThread::nullHandle()) - { - ov_clear(&mVF); - - // write "data" chunk length, in little-endian format - S32 data_length = mWAVBuffer.size() - WAV_HEADER_SIZE; - mWAVBuffer[40] = (data_length) & 0x000000FF; - mWAVBuffer[41] = (data_length >> 8) & 0x000000FF; - mWAVBuffer[42] = (data_length >> 16) & 0x000000FF; - mWAVBuffer[43] = (data_length >> 24) & 0x000000FF; - // write overall "RIFF" length, in little-endian format - data_length += 36; - mWAVBuffer[4] = (data_length) & 0x000000FF; - mWAVBuffer[5] = (data_length >> 8) & 0x000000FF; - mWAVBuffer[6] = (data_length >> 16) & 0x000000FF; - mWAVBuffer[7] = (data_length >> 24) & 0x000000FF; - - // - // FUDGECAKES!!! Vorbis encode/decode messes up loop point transitions (pop) - // do a cheap-and-cheesy crossfade - // - { - S16 *samplep; - S32 i; - S32 fade_length; - char pcmout[4096]; /*Flawfinder: ignore*/ - - fade_length = llmin((S32)128,(S32)(data_length-36)/8); - if((S32)mWAVBuffer.size() >= (WAV_HEADER_SIZE + 2* fade_length)) - { - memcpy(pcmout, &mWAVBuffer[WAV_HEADER_SIZE], (2 * fade_length)); /*Flawfinder: ignore*/ - } - llendianswizzle(&pcmout, 2, fade_length); - - samplep = (S16 *)pcmout; - for (i = 0 ;i < fade_length; i++) - { - *samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length)); - samplep++; - } - - llendianswizzle(&pcmout, 2, fade_length); - if((WAV_HEADER_SIZE+(2 * fade_length)) < (S32)mWAVBuffer.size()) - { - memcpy(&mWAVBuffer[WAV_HEADER_SIZE], pcmout, (2 * fade_length)); /*Flawfinder: ignore*/ - } - S32 near_end = mWAVBuffer.size() - (2 * fade_length); - if ((S32)mWAVBuffer.size() >= ( near_end + 2* fade_length)) - { - memcpy(pcmout, &mWAVBuffer[near_end], (2 * fade_length)); /*Flawfinder: ignore*/ - } - llendianswizzle(&pcmout, 2, fade_length); - - samplep = (S16 *)pcmout; - for (i = fade_length-1 ; i >= 0; i--) - { - *samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length)); - samplep++; - } - - llendianswizzle(&pcmout, 2, fade_length); - if (near_end + (2 * fade_length) < (S32)mWAVBuffer.size()) - { - memcpy(&mWAVBuffer[near_end], pcmout, (2 * fade_length));/*Flawfinder: ignore*/ - } - } - - if (36 == data_length) - { - LL_WARNS("AudioEngine") << "BAD Vorbis decode in finishDecode!" << LL_ENDL; - mValid = FALSE; - return TRUE; // we've finished - } - mBytesRead = -1; - mFileHandle = LLLFSThread::sLocal->write(mOutFilename, &mWAVBuffer[0], 0, mWAVBuffer.size(), - new WriteResponder(this)); - } - - if (mFileHandle != LLLFSThread::nullHandle()) - { - if (mBytesRead >= 0) - { - if (mBytesRead == 0) - { - LL_WARNS("AudioEngine") << "Unable to write file in LLVorbisDecodeState::finishDecode" << LL_ENDL; - mValid = FALSE; - return TRUE; // we've finished - } - } - else - { - return FALSE; // not done - } - } - - mDone = TRUE; - - LL_DEBUGS("AudioEngine") << "Finished decode for " << getUUID() << LL_ENDL; - - return TRUE; -} - -void LLVorbisDecodeState::flushBadFile() -{ - if (mInFilep) - { - LL_WARNS("AudioEngine") << "Flushing bad vorbis file from cache for " << mUUID << LL_ENDL; - mInFilep->remove(); - } -} - -////////////////////////////////////////////////////////////////////////////// - -class LLAudioDecodeMgr::Impl -{ - friend class LLAudioDecodeMgr; - Impl(); - public: - - void processQueue(); - - void startMoreDecodes(); - void enqueueFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState>& decode_state); - void checkDecodesFinished(); - - protected: - std::deque<LLUUID> mDecodeQueue; - std::map<LLUUID, LLPointer<LLVorbisDecodeState>> mDecodes; -}; - -LLAudioDecodeMgr::Impl::Impl() -{ -} - -// Returns the in-progress decode_state, which may be an empty LLPointer if -// there was an error and there is no more work to be done. -LLPointer<LLVorbisDecodeState> beginDecodingAndWritingAudio(const LLUUID &decode_id); - -// Return true if finished -bool tryFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState> decode_state); - -void LLAudioDecodeMgr::Impl::processQueue() -{ - // First, check if any audio from in-progress decodes are ready to play. If - // so, mark them ready for playback (or errored, in case of error). - checkDecodesFinished(); - - // Second, start as many decodes from the queue as permitted - startMoreDecodes(); -} - -void LLAudioDecodeMgr::Impl::startMoreDecodes() -{ - llassert_always(gAudiop); - - LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop"); - // *NOTE: main_queue->postTo casts this refcounted smart pointer to a weak - // pointer - LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General"); - const LL::ThreadPool::ptr_t general_thread_pool = LL::ThreadPool::getInstance("General"); - llassert_always(main_queue); - llassert_always(general_queue); - llassert_always(general_thread_pool); - // Set max decodes to double the thread count of the general work queue. - // This ensures the general work queue is full, but prevents theoretical - // buildup of buffers in memory due to disk writes once the - // LLVorbisDecodeState leaves the worker thread (see - // LLLFSThread::sLocal->write). This is probably as fast as we can get it - // without modifying/removing LLVorbisDecodeState, at which point we should - // consider decoding the audio during the asset download process. - // -Cosmic,2022-05-11 - const size_t max_decodes = general_thread_pool->getWidth() * 2; - - while (!mDecodeQueue.empty() && mDecodes.size() < max_decodes) - { - const LLUUID decode_id = mDecodeQueue.front(); - mDecodeQueue.pop_front(); - - // Don't decode the same file twice - if (mDecodes.find(decode_id) != mDecodes.end()) - { - continue; - } - if (gAudiop->hasDecodedFile(decode_id)) - { - continue; - } - - // Kick off a decode - mDecodes[decode_id] = LLPointer<LLVorbisDecodeState>(NULL); - bool posted = main_queue->postTo( - general_queue, - [decode_id]() // Work done on general queue - { - LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id); - - if (!decode_state) - { - // Audio decode has errored - return decode_state; - } - - // Disk write of decoded audio is now in progress off-thread - return decode_state; - }, - [decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread - mutable { - if (!gAudiop) - { - // There is no LLAudioEngine anymore. This might happen if - // an audio decode is enqueued just before shutdown. - return; - } - - // At this point, we can be certain that the pointer to "this" - // is valid because the lifetime of "this" is dependent upon - // the lifetime of gAudiop. - - enqueueFinishAudio(decode_id, decode_state); - }); - if (! posted) - { - // Shutdown - // Consider making processQueue() do a cleanup instead - // of starting more decodes - LL_WARNS() << "Tried to start decoding on shutdown" << LL_ENDL; - } - } -} - -LLPointer<LLVorbisDecodeState> beginDecodingAndWritingAudio(const LLUUID &decode_id) -{ - LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA; - - LL_DEBUGS() << "Decoding " << decode_id << " from audio queue!" << LL_ENDL; - - std::string d_path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, decode_id.asString()) + ".dsf"; - LLPointer<LLVorbisDecodeState> decode_state = new LLVorbisDecodeState(decode_id, d_path); - - if (!decode_state->initDecode()) - { - return NULL; - } - - // Decode in a loop until we're done - while (!decode_state->decodeSection()) - { - // decodeSection does all of the work above - } - - if (!decode_state->isDone()) - { - // Decode stopped early, or something bad happened to the file - // during decoding. - LL_WARNS("AudioEngine") << decode_id << " has invalid vorbis data or decode has been canceled, aborting decode" << LL_ENDL; - decode_state->flushBadFile(); - return NULL; - } - - if (!decode_state->isValid()) - { - // We had an error when decoding, abort. - LL_WARNS("AudioEngine") << decode_id << " has invalid vorbis data, aborting decode" << LL_ENDL; - decode_state->flushBadFile(); - return NULL; - } - - // Kick off the writing of the decoded audio to the disk cache. - // The receiving thread can then cheaply call finishDecode() again to check - // if writing has finished. Someone has to hold on to the refcounted - // decode_state to prevent it from getting destroyed during write. - decode_state->finishDecode(); - - return decode_state; -} - -void LLAudioDecodeMgr::Impl::enqueueFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState>& decode_state) -{ - // Assumed fast - if (tryFinishAudio(decode_id, decode_state)) - { - // Done early! - auto decode_iter = mDecodes.find(decode_id); - llassert(decode_iter != mDecodes.end()); - mDecodes.erase(decode_iter); - return; - } - - // Not done yet... enqueue it - mDecodes[decode_id] = decode_state; -} - -void LLAudioDecodeMgr::Impl::checkDecodesFinished() -{ - auto decode_iter = mDecodes.begin(); - while (decode_iter != mDecodes.end()) - { - const LLUUID& decode_id = decode_iter->first; - const LLPointer<LLVorbisDecodeState>& decode_state = decode_iter->second; - if (tryFinishAudio(decode_id, decode_state)) - { - decode_iter = mDecodes.erase(decode_iter); - } - else - { - ++decode_iter; - } - } -} - -bool tryFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState> decode_state) -{ - // decode_state is a file write in progress unless finished is true - bool finished = decode_state && decode_state->finishDecode(); - if (!finished) - { - return false; - } - - llassert_always(gAudiop); - - LLAudioData *adp = gAudiop->getAudioData(decode_id); - if (!adp) - { - LL_WARNS("AudioEngine") << "Missing LLAudioData for decode of " << decode_id << LL_ENDL; - return true; - } - - bool valid = decode_state && decode_state->isValid(); - // Mark current decode finished regardless of success or failure - adp->setHasCompletedDecode(true); - // Flip flags for decoded data - adp->setHasDecodeFailed(!valid); - adp->setHasDecodedData(valid); - // When finished decoding, there will also be a decoded wav file cached on - // disk with the .dsf extension - if (valid) - { - adp->setHasWAVLoadFailed(false); - } - - return true; -} - -////////////////////////////////////////////////////////////////////////////// - -LLAudioDecodeMgr::LLAudioDecodeMgr() -{ - mImpl = new Impl(); -} - -LLAudioDecodeMgr::~LLAudioDecodeMgr() -{ - delete mImpl; - mImpl = nullptr; -} - -void LLAudioDecodeMgr::processQueue() -{ - mImpl->processQueue(); -} - -BOOL LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid) -{ - if (gAudiop && gAudiop->hasDecodedFile(uuid)) - { - // Already have a decoded version, don't need to decode it. - LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has decoded file already" << LL_ENDL; - return TRUE; - } - - if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND)) - { - // Just put it on the decode queue. - LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has local asset file already" << LL_ENDL; - mImpl->mDecodeQueue.push_back(uuid); - return TRUE; - } - - LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " no file available" << LL_ENDL; - return FALSE; -} +/**
+ * @file llaudiodecodemgr.cpp
+ *
+ * $LicenseInfo:firstyear=2003&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 "llaudiodecodemgr.h"
+
+#include "llaudioengine.h"
+#include "lllfsthread.h"
+#include "llfilesystem.h"
+#include "llstring.h"
+#include "lldir.h"
+#include "llendianswizzle.h"
+#include "llassetstorage.h"
+#include "llrefcount.h"
+#include "threadpool.h"
+#include "workqueue.h"
+
+#include "llvorbisencode.h"
+
+#include "vorbis/codec.h"
+#include "vorbis/vorbisfile.h"
+#include <iterator>
+#include <deque>
+
+extern LLAudioEngine *gAudiop;
+
+static const S32 WAV_HEADER_SIZE = 44;
+
+
+//////////////////////////////////////////////////////////////////////////////
+
+
+class LLVorbisDecodeState : public LLThreadSafeRefCount
+{
+public:
+ class WriteResponder : public LLLFSThread::Responder
+ {
+ public:
+ WriteResponder(LLVorbisDecodeState* decoder) : mDecoder(decoder) {}
+ ~WriteResponder() {}
+ void completed(S32 bytes)
+ {
+ mDecoder->ioComplete(bytes);
+ }
+ LLPointer<LLVorbisDecodeState> mDecoder;
+ };
+
+ LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename);
+
+ bool initDecode();
+ bool decodeSection(); // Return true if done.
+ bool finishDecode();
+
+ void flushBadFile();
+
+ void ioComplete(S32 bytes) { mBytesRead = bytes; }
+ bool isValid() const { return mValid; }
+ bool isDone() const { return mDone; }
+ const LLUUID &getUUID() const { return mUUID; }
+
+protected:
+ virtual ~LLVorbisDecodeState();
+
+ bool mValid;
+ bool mDone;
+ LLAtomicS32 mBytesRead;
+ LLUUID mUUID;
+
+ std::vector<U8> mWAVBuffer;
+ std::string mOutFilename;
+ LLLFSThread::handle_t mFileHandle;
+
+ LLFileSystem *mInFilep;
+ OggVorbis_File mVF;
+ S32 mCurrentSection;
+};
+
+size_t cache_read(void *ptr, size_t size, size_t nmemb, void *datasource)
+{
+ LLFileSystem *file = (LLFileSystem *)datasource;
+
+ if (file->read((U8*)ptr, (S32)(size * nmemb))) /*Flawfinder: ignore*/
+ {
+ S32 read = file->getLastBytesRead();
+ return read / size; /*Flawfinder: ignore*/
+ }
+ else
+ {
+ return 0;
+ }
+}
+
+S32 cache_seek(void *datasource, ogg_int64_t offset, S32 whence)
+{
+ LLFileSystem *file = (LLFileSystem *)datasource;
+
+ // cache has 31-bit files
+ if (offset > S32_MAX)
+ {
+ return -1;
+ }
+
+ S32 origin;
+ switch (whence) {
+ case SEEK_SET:
+ origin = 0;
+ break;
+ case SEEK_END:
+ origin = file->getSize();
+ break;
+ case SEEK_CUR:
+ origin = -1;
+ break;
+ default:
+ LL_ERRS("AudioEngine") << "Invalid whence argument to cache_seek" << LL_ENDL;
+ return -1;
+ }
+
+ if (file->seek((S32)offset, origin))
+ {
+ return 0;
+ }
+ else
+ {
+ return -1;
+ }
+}
+
+S32 cache_close (void *datasource)
+{
+ LLFileSystem *file = (LLFileSystem *)datasource;
+ delete file;
+ return 0;
+}
+
+long cache_tell (void *datasource)
+{
+ LLFileSystem *file = (LLFileSystem *)datasource;
+ return file->tell();
+}
+
+LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const std::string &out_filename)
+{
+ mDone = false;
+ mValid = false;
+ mBytesRead = -1;
+ mUUID = uuid;
+ mInFilep = NULL;
+ mCurrentSection = 0;
+ mOutFilename = out_filename;
+ mFileHandle = LLLFSThread::nullHandle();
+
+ // No default value for mVF, it's an ogg structure?
+ // Hey, let's zero it anyway, for predictability.
+ memset(&mVF, 0, sizeof(mVF));
+}
+
+LLVorbisDecodeState::~LLVorbisDecodeState()
+{
+ if (!mDone)
+ {
+ delete mInFilep;
+ mInFilep = NULL;
+ }
+}
+
+
+bool LLVorbisDecodeState::initDecode()
+{
+ ov_callbacks cache_callbacks;
+ cache_callbacks.read_func = cache_read;
+ cache_callbacks.seek_func = cache_seek;
+ cache_callbacks.close_func = cache_close;
+ cache_callbacks.tell_func = cache_tell;
+
+ LL_DEBUGS("AudioEngine") << "Initing decode from vfile: " << mUUID << LL_ENDL;
+
+ mInFilep = new LLFileSystem(mUUID, LLAssetType::AT_SOUND);
+ if (!mInFilep || !mInFilep->getSize())
+ {
+ LL_WARNS("AudioEngine") << "unable to open vorbis source vfile for reading" << LL_ENDL;
+ delete mInFilep;
+ mInFilep = NULL;
+ return false;
+ }
+
+ S32 r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, cache_callbacks);
+ if(r < 0)
+ {
+ LL_WARNS("AudioEngine") << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << LL_ENDL;
+ return(false);
+ }
+
+ S32 sample_count = (S32)ov_pcm_total(&mVF, -1);
+ size_t size_guess = (size_t)sample_count;
+ vorbis_info* vi = ov_info(&mVF, -1);
+ size_guess *= (vi? vi->channels : 1);
+ size_guess *= 2;
+ size_guess += 2048;
+
+ bool abort_decode = false;
+
+ if (vi)
+ {
+ if( vi->channels < 1 || vi->channels > LLVORBIS_CLIP_MAX_CHANNELS )
+ {
+ abort_decode = true;
+ LL_WARNS("AudioEngine") << "Bad channel count: " << vi->channels << LL_ENDL;
+ }
+ }
+ else // !vi
+ {
+ abort_decode = true;
+ LL_WARNS("AudioEngine") << "No default bitstream found" << LL_ENDL;
+ }
+
+ if( (size_t)sample_count > LLVORBIS_CLIP_REJECT_SAMPLES ||
+ (size_t)sample_count <= 0)
+ {
+ abort_decode = true;
+ LL_WARNS("AudioEngine") << "Illegal sample count: " << sample_count << LL_ENDL;
+ }
+
+ if( size_guess > LLVORBIS_CLIP_REJECT_SIZE )
+ {
+ abort_decode = true;
+ LL_WARNS("AudioEngine") << "Illegal sample size: " << size_guess << LL_ENDL;
+ }
+
+ if( abort_decode )
+ {
+ LL_WARNS("AudioEngine") << "Canceling initDecode. Bad asset: " << mUUID << LL_ENDL;
+ vorbis_comment* comment = ov_comment(&mVF,-1);
+ if (comment && comment->vendor)
+ {
+ LL_WARNS("AudioEngine") << "Bad asset encoded by: " << comment->vendor << LL_ENDL;
+ }
+ delete mInFilep;
+ mInFilep = NULL;
+ return false;
+ }
+
+ try
+ {
+ mWAVBuffer.reserve(size_guess);
+ mWAVBuffer.resize(WAV_HEADER_SIZE);
+ }
+ catch (std::bad_alloc&)
+ {
+ LL_WARNS("AudioEngine") << "Out of memory when trying to alloc buffer: " << size_guess << LL_ENDL;
+ delete mInFilep;
+ mInFilep = NULL;
+ return false;
+ }
+
+ {
+ // write the .wav format header
+ //"RIFF"
+ mWAVBuffer[0] = 0x52;
+ mWAVBuffer[1] = 0x49;
+ mWAVBuffer[2] = 0x46;
+ mWAVBuffer[3] = 0x46;
+
+ // length = datalen + 36 (to be filled in later)
+ mWAVBuffer[4] = 0x00;
+ mWAVBuffer[5] = 0x00;
+ mWAVBuffer[6] = 0x00;
+ mWAVBuffer[7] = 0x00;
+
+ //"WAVE"
+ mWAVBuffer[8] = 0x57;
+ mWAVBuffer[9] = 0x41;
+ mWAVBuffer[10] = 0x56;
+ mWAVBuffer[11] = 0x45;
+
+ // "fmt "
+ mWAVBuffer[12] = 0x66;
+ mWAVBuffer[13] = 0x6D;
+ mWAVBuffer[14] = 0x74;
+ mWAVBuffer[15] = 0x20;
+
+ // chunk size = 16
+ mWAVBuffer[16] = 0x10;
+ mWAVBuffer[17] = 0x00;
+ mWAVBuffer[18] = 0x00;
+ mWAVBuffer[19] = 0x00;
+
+ // format (1 = PCM)
+ mWAVBuffer[20] = 0x01;
+ mWAVBuffer[21] = 0x00;
+
+ // number of channels
+ mWAVBuffer[22] = 0x01;
+ mWAVBuffer[23] = 0x00;
+
+ // samples per second
+ mWAVBuffer[24] = 0x44;
+ mWAVBuffer[25] = 0xAC;
+ mWAVBuffer[26] = 0x00;
+ mWAVBuffer[27] = 0x00;
+
+ // average bytes per second
+ mWAVBuffer[28] = 0x88;
+ mWAVBuffer[29] = 0x58;
+ mWAVBuffer[30] = 0x01;
+ mWAVBuffer[31] = 0x00;
+
+ // bytes to output at a single time
+ mWAVBuffer[32] = 0x02;
+ mWAVBuffer[33] = 0x00;
+
+ // 16 bits per sample
+ mWAVBuffer[34] = 0x10;
+ mWAVBuffer[35] = 0x00;
+
+ // "data"
+ mWAVBuffer[36] = 0x64;
+ mWAVBuffer[37] = 0x61;
+ mWAVBuffer[38] = 0x74;
+ mWAVBuffer[39] = 0x61;
+
+ // these are the length of the data chunk, to be filled in later
+ mWAVBuffer[40] = 0x00;
+ mWAVBuffer[41] = 0x00;
+ mWAVBuffer[42] = 0x00;
+ mWAVBuffer[43] = 0x00;
+ }
+
+ //{
+ //char **ptr=ov_comment(&mVF,-1)->user_comments;
+// vorbis_info *vi=ov_info(&vf,-1);
+ //while(*ptr){
+ // fprintf(stderr,"%s\n",*ptr);
+ // ++ptr;
+ //}
+// fprintf(stderr,"\nBitstream is %d channel, %ldHz\n",vi->channels,vi->rate);
+// fprintf(stderr,"\nDecoded length: %ld samples\n", (long)ov_pcm_total(&vf,-1));
+// fprintf(stderr,"Encoded by: %s\n\n",ov_comment(&vf,-1)->vendor);
+ //}
+ return true;
+}
+
+bool LLVorbisDecodeState::decodeSection()
+{
+ if (!mInFilep)
+ {
+ LL_WARNS("AudioEngine") << "No cache file to decode in vorbis!" << LL_ENDL;
+ return true;
+ }
+ if (mDone)
+ {
+// LL_WARNS("AudioEngine") << "Already done with decode, aborting!" << LL_ENDL;
+ return true;
+ }
+ char pcmout[4096]; /*Flawfinder: ignore*/
+
+ bool eof = false;
+ long ret=ov_read(&mVF, pcmout, sizeof(pcmout), 0, 2, 1, &mCurrentSection);
+ if (ret == 0)
+ {
+ /* EOF */
+ eof = true;
+ mDone = true;
+ mValid = true;
+// LL_INFOS("AudioEngine") << "Vorbis EOF" << LL_ENDL;
+ }
+ else if (ret < 0)
+ {
+ /* error in the stream. Not a problem, just reporting it in
+ case we (the app) cares. In this case, we don't. */
+
+ LL_WARNS("AudioEngine") << "BAD vorbis decode in decodeSection." << LL_ENDL;
+
+ mValid = false;
+ mDone = true;
+ // We're done, return true.
+ return true;
+ }
+ else
+ {
+// LL_INFOS("AudioEngine") << "Vorbis read " << ret << "bytes" << LL_ENDL;
+ /* we don't bother dealing with sample rate changes, etc, but.
+ you'll have to*/
+ std::copy(pcmout, pcmout+ret, std::back_inserter(mWAVBuffer));
+ }
+ return eof;
+}
+
+bool LLVorbisDecodeState::finishDecode()
+{
+ if (!isValid())
+ {
+ LL_WARNS("AudioEngine") << "Bogus vorbis decode state for " << getUUID() << ", aborting!" << LL_ENDL;
+ return true; // We've finished
+ }
+
+ if (mFileHandle == LLLFSThread::nullHandle())
+ {
+ ov_clear(&mVF);
+
+ // write "data" chunk length, in little-endian format
+ S32 data_length = mWAVBuffer.size() - WAV_HEADER_SIZE;
+ mWAVBuffer[40] = (data_length) & 0x000000FF;
+ mWAVBuffer[41] = (data_length >> 8) & 0x000000FF;
+ mWAVBuffer[42] = (data_length >> 16) & 0x000000FF;
+ mWAVBuffer[43] = (data_length >> 24) & 0x000000FF;
+ // write overall "RIFF" length, in little-endian format
+ data_length += 36;
+ mWAVBuffer[4] = (data_length) & 0x000000FF;
+ mWAVBuffer[5] = (data_length >> 8) & 0x000000FF;
+ mWAVBuffer[6] = (data_length >> 16) & 0x000000FF;
+ mWAVBuffer[7] = (data_length >> 24) & 0x000000FF;
+
+ //
+ // FUDGECAKES!!! Vorbis encode/decode messes up loop point transitions (pop)
+ // do a cheap-and-cheesy crossfade
+ //
+ {
+ S16 *samplep;
+ S32 i;
+ S32 fade_length;
+ char pcmout[4096]; /*Flawfinder: ignore*/
+
+ fade_length = llmin((S32)128,(S32)(data_length-36)/8);
+ if((S32)mWAVBuffer.size() >= (WAV_HEADER_SIZE + 2* fade_length))
+ {
+ memcpy(pcmout, &mWAVBuffer[WAV_HEADER_SIZE], (2 * fade_length)); /*Flawfinder: ignore*/
+ }
+ llendianswizzle(&pcmout, 2, fade_length);
+
+ samplep = (S16 *)pcmout;
+ for (i = 0 ;i < fade_length; i++)
+ {
+ *samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length));
+ samplep++;
+ }
+
+ llendianswizzle(&pcmout, 2, fade_length);
+ if((WAV_HEADER_SIZE+(2 * fade_length)) < (S32)mWAVBuffer.size())
+ {
+ memcpy(&mWAVBuffer[WAV_HEADER_SIZE], pcmout, (2 * fade_length)); /*Flawfinder: ignore*/
+ }
+ S32 near_end = mWAVBuffer.size() - (2 * fade_length);
+ if ((S32)mWAVBuffer.size() >= ( near_end + 2* fade_length))
+ {
+ memcpy(pcmout, &mWAVBuffer[near_end], (2 * fade_length)); /*Flawfinder: ignore*/
+ }
+ llendianswizzle(&pcmout, 2, fade_length);
+
+ samplep = (S16 *)pcmout;
+ for (i = fade_length-1 ; i >= 0; i--)
+ {
+ *samplep = llfloor((F32)*samplep * ((F32)i/(F32)fade_length));
+ samplep++;
+ }
+
+ llendianswizzle(&pcmout, 2, fade_length);
+ if (near_end + (2 * fade_length) < (S32)mWAVBuffer.size())
+ {
+ memcpy(&mWAVBuffer[near_end], pcmout, (2 * fade_length));/*Flawfinder: ignore*/
+ }
+ }
+
+ if (36 == data_length)
+ {
+ LL_WARNS("AudioEngine") << "BAD Vorbis decode in finishDecode!" << LL_ENDL;
+ mValid = false;
+ return true; // we've finished
+ }
+ mBytesRead = -1;
+ mFileHandle = LLLFSThread::sLocal->write(mOutFilename, &mWAVBuffer[0], 0, mWAVBuffer.size(),
+ new WriteResponder(this));
+ }
+
+ if (mFileHandle != LLLFSThread::nullHandle())
+ {
+ if (mBytesRead >= 0)
+ {
+ if (mBytesRead == 0)
+ {
+ LL_WARNS("AudioEngine") << "Unable to write file in LLVorbisDecodeState::finishDecode" << LL_ENDL;
+ mValid = false;
+ return true; // we've finished
+ }
+ }
+ else
+ {
+ return false; // not done
+ }
+ }
+
+ mDone = true;
+
+ LL_DEBUGS("AudioEngine") << "Finished decode for " << getUUID() << LL_ENDL;
+
+ return true;
+}
+
+void LLVorbisDecodeState::flushBadFile()
+{
+ if (mInFilep)
+ {
+ LL_WARNS("AudioEngine") << "Flushing bad vorbis file from cache for " << mUUID << LL_ENDL;
+ mInFilep->remove();
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+class LLAudioDecodeMgr::Impl
+{
+ friend class LLAudioDecodeMgr;
+ Impl();
+ public:
+
+ void processQueue();
+
+ void startMoreDecodes();
+ void enqueueFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState>& decode_state);
+ void checkDecodesFinished();
+
+ protected:
+ std::deque<LLUUID> mDecodeQueue;
+ std::map<LLUUID, LLPointer<LLVorbisDecodeState>> mDecodes;
+};
+
+LLAudioDecodeMgr::Impl::Impl()
+{
+}
+
+// Returns the in-progress decode_state, which may be an empty LLPointer if
+// there was an error and there is no more work to be done.
+LLPointer<LLVorbisDecodeState> beginDecodingAndWritingAudio(const LLUUID &decode_id);
+
+// Return true if finished
+bool tryFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState> decode_state);
+
+void LLAudioDecodeMgr::Impl::processQueue()
+{
+ // First, check if any audio from in-progress decodes are ready to play. If
+ // so, mark them ready for playback (or errored, in case of error).
+ checkDecodesFinished();
+
+ // Second, start as many decodes from the queue as permitted
+ startMoreDecodes();
+}
+
+void LLAudioDecodeMgr::Impl::startMoreDecodes()
+{
+ llassert_always(gAudiop);
+
+ LL::WorkQueue::ptr_t main_queue = LL::WorkQueue::getInstance("mainloop");
+ // *NOTE: main_queue->postTo casts this refcounted smart pointer to a weak
+ // pointer
+ LL::WorkQueue::ptr_t general_queue = LL::WorkQueue::getInstance("General");
+ const LL::ThreadPool::ptr_t general_thread_pool = LL::ThreadPool::getInstance("General");
+ llassert_always(main_queue);
+ llassert_always(general_queue);
+ llassert_always(general_thread_pool);
+ // Set max decodes to double the thread count of the general work queue.
+ // This ensures the general work queue is full, but prevents theoretical
+ // buildup of buffers in memory due to disk writes once the
+ // LLVorbisDecodeState leaves the worker thread (see
+ // LLLFSThread::sLocal->write). This is probably as fast as we can get it
+ // without modifying/removing LLVorbisDecodeState, at which point we should
+ // consider decoding the audio during the asset download process.
+ // -Cosmic,2022-05-11
+ const size_t max_decodes = general_thread_pool->getWidth() * 2;
+
+ while (!mDecodeQueue.empty() && mDecodes.size() < max_decodes)
+ {
+ const LLUUID decode_id = mDecodeQueue.front();
+ mDecodeQueue.pop_front();
+
+ // Don't decode the same file twice
+ if (mDecodes.find(decode_id) != mDecodes.end())
+ {
+ continue;
+ }
+ if (gAudiop->hasDecodedFile(decode_id))
+ {
+ continue;
+ }
+
+ // Kick off a decode
+ mDecodes[decode_id] = LLPointer<LLVorbisDecodeState>(NULL);
+ bool posted = main_queue->postTo(
+ general_queue,
+ [decode_id]() // Work done on general queue
+ {
+ LLPointer<LLVorbisDecodeState> decode_state = beginDecodingAndWritingAudio(decode_id);
+
+ if (!decode_state)
+ {
+ // Audio decode has errored
+ return decode_state;
+ }
+
+ // Disk write of decoded audio is now in progress off-thread
+ return decode_state;
+ },
+ [decode_id, this](LLPointer<LLVorbisDecodeState> decode_state) // Callback to main thread
+ mutable {
+ if (!gAudiop)
+ {
+ // There is no LLAudioEngine anymore. This might happen if
+ // an audio decode is enqueued just before shutdown.
+ return;
+ }
+
+ // At this point, we can be certain that the pointer to "this"
+ // is valid because the lifetime of "this" is dependent upon
+ // the lifetime of gAudiop.
+
+ enqueueFinishAudio(decode_id, decode_state);
+ });
+ if (! posted)
+ {
+ // Shutdown
+ // Consider making processQueue() do a cleanup instead
+ // of starting more decodes
+ LL_WARNS() << "Tried to start decoding on shutdown" << LL_ENDL;
+ }
+ }
+}
+
+LLPointer<LLVorbisDecodeState> beginDecodingAndWritingAudio(const LLUUID &decode_id)
+{
+ LL_PROFILE_ZONE_SCOPED_CATEGORY_MEDIA;
+
+ LL_DEBUGS() << "Decoding " << decode_id << " from audio queue!" << LL_ENDL;
+
+ std::string d_path = gDirUtilp->getExpandedFilename(LL_PATH_CACHE, decode_id.asString()) + ".dsf";
+ LLPointer<LLVorbisDecodeState> decode_state = new LLVorbisDecodeState(decode_id, d_path);
+
+ if (!decode_state->initDecode())
+ {
+ return NULL;
+ }
+
+ // Decode in a loop until we're done
+ while (!decode_state->decodeSection())
+ {
+ // decodeSection does all of the work above
+ }
+
+ if (!decode_state->isDone())
+ {
+ // Decode stopped early, or something bad happened to the file
+ // during decoding.
+ LL_WARNS("AudioEngine") << decode_id << " has invalid vorbis data or decode has been canceled, aborting decode" << LL_ENDL;
+ decode_state->flushBadFile();
+ return NULL;
+ }
+
+ if (!decode_state->isValid())
+ {
+ // We had an error when decoding, abort.
+ LL_WARNS("AudioEngine") << decode_id << " has invalid vorbis data, aborting decode" << LL_ENDL;
+ decode_state->flushBadFile();
+ return NULL;
+ }
+
+ // Kick off the writing of the decoded audio to the disk cache.
+ // The receiving thread can then cheaply call finishDecode() again to check
+ // if writing has finished. Someone has to hold on to the refcounted
+ // decode_state to prevent it from getting destroyed during write.
+ decode_state->finishDecode();
+
+ return decode_state;
+}
+
+void LLAudioDecodeMgr::Impl::enqueueFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState>& decode_state)
+{
+ // Assumed fast
+ if (tryFinishAudio(decode_id, decode_state))
+ {
+ // Done early!
+ auto decode_iter = mDecodes.find(decode_id);
+ llassert(decode_iter != mDecodes.end());
+ mDecodes.erase(decode_iter);
+ return;
+ }
+
+ // Not done yet... enqueue it
+ mDecodes[decode_id] = decode_state;
+}
+
+void LLAudioDecodeMgr::Impl::checkDecodesFinished()
+{
+ auto decode_iter = mDecodes.begin();
+ while (decode_iter != mDecodes.end())
+ {
+ const LLUUID& decode_id = decode_iter->first;
+ const LLPointer<LLVorbisDecodeState>& decode_state = decode_iter->second;
+ if (tryFinishAudio(decode_id, decode_state))
+ {
+ decode_iter = mDecodes.erase(decode_iter);
+ }
+ else
+ {
+ ++decode_iter;
+ }
+ }
+}
+
+bool tryFinishAudio(const LLUUID &decode_id, LLPointer<LLVorbisDecodeState> decode_state)
+{
+ // decode_state is a file write in progress unless finished is true
+ bool finished = decode_state && decode_state->finishDecode();
+ if (!finished)
+ {
+ return false;
+ }
+
+ llassert_always(gAudiop);
+
+ LLAudioData *adp = gAudiop->getAudioData(decode_id);
+ if (!adp)
+ {
+ LL_WARNS("AudioEngine") << "Missing LLAudioData for decode of " << decode_id << LL_ENDL;
+ return true;
+ }
+
+ bool valid = decode_state && decode_state->isValid();
+ // Mark current decode finished regardless of success or failure
+ adp->setHasCompletedDecode(true);
+ // Flip flags for decoded data
+ adp->setHasDecodeFailed(!valid);
+ adp->setHasDecodedData(valid);
+ // When finished decoding, there will also be a decoded wav file cached on
+ // disk with the .dsf extension
+ if (valid)
+ {
+ adp->setHasWAVLoadFailed(false);
+ }
+
+ return true;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+
+LLAudioDecodeMgr::LLAudioDecodeMgr()
+{
+ mImpl = new Impl();
+}
+
+LLAudioDecodeMgr::~LLAudioDecodeMgr()
+{
+ delete mImpl;
+ mImpl = nullptr;
+}
+
+void LLAudioDecodeMgr::processQueue()
+{
+ mImpl->processQueue();
+}
+
+bool LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid)
+{
+ if (gAudiop && gAudiop->hasDecodedFile(uuid))
+ {
+ // Already have a decoded version, don't need to decode it.
+ LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has decoded file already" << LL_ENDL;
+ return true;
+ }
+
+ if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND))
+ {
+ // Just put it on the decode queue.
+ LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " has local asset file already" << LL_ENDL;
+ mImpl->mDecodeQueue.push_back(uuid);
+ return true;
+ }
+
+ LL_DEBUGS("AudioEngine") << "addDecodeRequest for " << uuid << " no file available" << LL_ENDL;
+ return false;
+}
diff --git a/indra/llaudio/llaudiodecodemgr.h b/indra/llaudio/llaudiodecodemgr.h index 02d5c67587..17fa31ee53 100644 --- a/indra/llaudio/llaudiodecodemgr.h +++ b/indra/llaudio/llaudiodecodemgr.h @@ -1,54 +1,54 @@ -/** - * @file llaudiodecodemgr.h - * - * $LicenseInfo:firstyear=2003&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$ - */ - -#ifndef LL_LLAUDIODECODEMGR_H -#define LL_LLAUDIODECODEMGR_H - -#include "stdtypes.h" - -#include "lluuid.h" - -#include "llassettype.h" -#include "llframetimer.h" -#include "llsingleton.h" - -template<class T> class LLPointer; -class LLVorbisDecodeState; - -class LLAudioDecodeMgr : public LLSingleton<LLAudioDecodeMgr> -{ - LLSINGLETON(LLAudioDecodeMgr); - ~LLAudioDecodeMgr(); -public: - void processQueue(); - BOOL addDecodeRequest(const LLUUID &uuid); - void addAudioRequest(const LLUUID &uuid); - -protected: - class Impl; - Impl* mImpl; -}; - -#endif +/**
+ * @file llaudiodecodemgr.h
+ *
+ * $LicenseInfo:firstyear=2003&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$
+ */
+
+#ifndef LL_LLAUDIODECODEMGR_H
+#define LL_LLAUDIODECODEMGR_H
+
+#include "stdtypes.h"
+
+#include "lluuid.h"
+
+#include "llassettype.h"
+#include "llframetimer.h"
+#include "llsingleton.h"
+
+template<class T> class LLPointer;
+class LLVorbisDecodeState;
+
+class LLAudioDecodeMgr : public LLSingleton<LLAudioDecodeMgr>
+{
+ LLSINGLETON(LLAudioDecodeMgr);
+ ~LLAudioDecodeMgr();
+public:
+ void processQueue();
+ bool addDecodeRequest(const LLUUID &uuid);
+ void addAudioRequest(const LLUUID &uuid);
+
+protected:
+ class Impl;
+ Impl* mImpl;
+};
+
+#endif
diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h index 2a16050c5b..9949b8f337 100755 --- a/indra/llaudio/llaudioengine.h +++ b/indra/llaudio/llaudioengine.h @@ -1,486 +1,486 @@ -/** - * @file audioengine.h - * @brief Definition of LLAudioEngine base class abstracting the 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$ - */ - - -#ifndef LL_AUDIOENGINE_H -#define LL_AUDIOENGINE_H - -#include <list> -#include <map> -#include <array> - -#include "v3math.h" -#include "v3dmath.h" -#include "lltimer.h" -#include "lluuid.h" -#include "llframetimer.h" -#include "llassettype.h" -#include "llextendedstatus.h" - -#include "lllistener.h" - -const F32 LL_WIND_UPDATE_INTERVAL = 0.1f; -const F32 LL_WIND_UNDERWATER_CENTER_FREQ = 20.f; - -const F32 ATTACHED_OBJECT_TIMEOUT = 5.0f; -const F32 DEFAULT_MIN_DISTANCE = 2.0f; - -#define LL_MAX_AUDIO_CHANNELS 30 -#define LL_MAX_AUDIO_BUFFERS 40 // Some extra for preloading, maybe? - -class LLAudioSource; -class LLAudioData; -class LLAudioChannel; -class LLAudioChannelOpenAL; -class LLAudioBuffer; -class LLStreamingAudioInterface; -struct SoundData; - -// -// LLAudioEngine definition -// -class LLAudioEngine -{ - friend class LLAudioChannelOpenAL; // bleh. channel needs some listener methods. - -public: - enum LLAudioType - { - AUDIO_TYPE_NONE = 0, - AUDIO_TYPE_SFX = 1, - AUDIO_TYPE_UI = 2, - AUDIO_TYPE_AMBIENT = 3, - AUDIO_TYPE_COUNT = 4 // last - }; - - enum LLAudioPlayState - { - // isInternetStreamPlaying() returns an *S32*, with - // 0 = stopped, 1 = playing, 2 = paused. - AUDIO_STOPPED = 0, - AUDIO_PLAYING = 1, - AUDIO_PAUSED = 2 - }; - - LLAudioEngine(); - virtual ~LLAudioEngine(); - - // initialization/startup/shutdown - virtual bool init(void *userdata, const std::string &app_title); - virtual std::string getDriverName(bool verbose) = 0; - virtual LLStreamingAudioInterface *createDefaultStreamingAudioImpl() const = 0; - virtual void shutdown(); - - // Used by the mechanics of the engine - //virtual void processQueue(const LLUUID &sound_guid); - virtual void setListener(LLVector3 pos,LLVector3 vel,LLVector3 up,LLVector3 at); - virtual void updateWind(LLVector3 direction, F32 camera_height_above_water) = 0; - virtual void idle(); - virtual void updateChannels(); - - // - // "End user" functionality - // - virtual bool isWindEnabled(); - virtual void enableWind(bool state_b); - - // Use these for temporarily muting the audio system. - // Does not change buffers, initialization, etc. but - // stops playing new sounds. - void setMuted(bool muted); - bool getMuted() const { return mMuted; } -#ifdef USE_PLUGIN_MEDIA - LLPluginClassMedia* initializeMedia(const std::string& media_type); -#endif - F32 getMasterGain(); - void setMasterGain(F32 gain); - - F32 getSecondaryGain(S32 type); - void setSecondaryGain(S32 type, F32 gain); - - F32 getInternetStreamGain(); - - virtual void setDopplerFactor(F32 factor); - virtual F32 getDopplerFactor(); - virtual void setRolloffFactor(F32 factor); - virtual F32 getRolloffFactor(); - virtual void setMaxWindGain(F32 gain); - - - // Methods actually related to setting up and removing sounds - // Owner ID is the owner of the object making the request - void triggerSound(const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain, - const S32 type = LLAudioEngine::AUDIO_TYPE_NONE, - const LLVector3d &pos_global = LLVector3d::zero); - void triggerSound(SoundData& soundData); - - bool preloadSound(const LLUUID &id); - - void addAudioSource(LLAudioSource *asp); - void cleanupAudioSource(LLAudioSource *asp); - - LLAudioSource *findAudioSource(const LLUUID &source_id); - LLAudioData *getAudioData(const LLUUID &audio_uuid); - - // Internet stream implementation manipulation - LLStreamingAudioInterface *getStreamingAudioImpl(); - void setStreamingAudioImpl(LLStreamingAudioInterface *impl); - // Internet stream methods - these will call down into the *mStreamingAudioImpl if it exists - void startInternetStream(const std::string& url); - void stopInternetStream(); - void pauseInternetStream(S32 pause); - void updateInternetStream(); // expected to be called often - LLAudioPlayState isInternetStreamPlaying(); - // use a value from 0.0 to 1.0, inclusive - void setInternetStreamGain(F32 vol); - std::string getInternetStreamURL(); - - // For debugging usage - virtual LLVector3 getListenerPos(); - - LLAudioBuffer *getFreeBuffer(); // Get a free buffer, or flush an existing one if you have to. - LLAudioChannel *getFreeChannel(const F32 priority); // Get a free channel or flush an existing one if your priority is higher - void cleanupBuffer(LLAudioBuffer *bufferp); - - bool hasDecodedFile(const LLUUID &uuid); - bool hasLocalFile(const LLUUID &uuid); - - bool updateBufferForData(LLAudioData *adp, const LLUUID &audio_uuid = LLUUID::null); - - - // Asset callback when we're retrieved a sound from the asset server. - void startNextTransfer(); - static void assetCallback(const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status); - - friend class LLPipeline; // For debugging -public: - F32 mMaxWindGain; // Hack. Public to set before fade in? - -protected: - virtual LLAudioBuffer *createBuffer() = 0; - virtual LLAudioChannel *createChannel() = 0; - - virtual bool initWind() = 0; - virtual void cleanupWind() = 0; - virtual void setInternalGain(F32 gain) = 0; - - void commitDeferredChanges(); - - virtual void allocateListener() = 0; - - - // listener methods - virtual void setListenerPos(LLVector3 vec); - virtual void setListenerVelocity(LLVector3 vec); - virtual void orientListener(LLVector3 up, LLVector3 at); - virtual void translateListener(LLVector3 vec); - - - F64 mapWindVecToGain(LLVector3 wind_vec); - F64 mapWindVecToPitch(LLVector3 wind_vec); - F64 mapWindVecToPan(LLVector3 wind_vec); - -protected: - LLListener *mListenerp; - - bool mMuted; - void* mUserData; - - S32 mLastStatus; - - bool mEnableWind; - - LLUUID mCurrentTransfer; // Audio file currently being transferred by the system - LLFrameTimer mCurrentTransferTimer; - - // A list of all audio sources that are known to the viewer at this time. - // This is most likely a superset of the ones that we actually have audio - // data for, or are playing back. - typedef std::map<LLUUID, LLAudioSource *> source_map; - typedef std::map<LLUUID, LLAudioData *> data_map; - - source_map mAllSources; - data_map mAllData; - - std::array<LLAudioChannel*, LL_MAX_AUDIO_CHANNELS> mChannels; - - // Buffers needs to change into a different data structure, as the number of buffers - // that we have active should be limited by RAM usage, not count. - std::array<LLAudioBuffer*, LL_MAX_AUDIO_BUFFERS> mBuffers; - - F32 mMasterGain; - F32 mInternalGain; // Actual gain set; either mMasterGain or 0 when mMuted is true. - F32 mSecondaryGain[AUDIO_TYPE_COUNT]; - - F32 mNextWindUpdate; - - LLFrameTimer mWindUpdateTimer; - -private: - void setDefaults(); - LLStreamingAudioInterface *mStreamingAudioImpl; -}; - - - - -// -// Standard audio source. Can be derived from for special sources, such as those attached to objects. -// - - -class LLAudioSource -{ -public: - // owner_id is the id of the agent responsible for making this sound - // play, for example, the owner of the object currently playing it - LLAudioSource(const LLUUID &id, const LLUUID& owner_id, const F32 gain, const S32 type = LLAudioEngine::AUDIO_TYPE_NONE); - virtual ~LLAudioSource(); - - virtual void update(); // Update this audio source - void updatePriority(); - - void preload(const LLUUID &audio_id); // Only used for preloading UI sounds, now. - - void addAudioData(LLAudioData *adp, bool set_current = TRUE); - - void setForcedPriority(const bool ambient) { mForcedPriority = ambient; } - bool isForcedPriority() const { return mForcedPriority; } - - void setLoop(const bool loop) { mLoop = loop; } - bool isLoop() const { return mLoop; } - - void setSyncMaster(const bool master) { mSyncMaster = master; } - bool isSyncMaster() const { return mSyncMaster; } - - void setSyncSlave(const bool slave) { mSyncSlave = slave; } - bool isSyncSlave() const { return mSyncSlave; } - - void setQueueSounds(const bool queue) { mQueueSounds = queue; } - bool isQueueSounds() const { return mQueueSounds; } - - void setPlayedOnce(const bool played_once) { mPlayedOnce = played_once; } - - void setType(S32 type) { mType = type; } - S32 getType() { return mType; } - - void setPositionGlobal(const LLVector3d &position_global) { mPositionGlobal = position_global; } - LLVector3d getPositionGlobal() const { return mPositionGlobal; } - LLVector3 getVelocity() const { return mVelocity; } - F32 getPriority() const { return mPriority; } - - // Gain should always be clamped between 0 and 1. - F32 getGain() const { return mGain; } - virtual void setGain(const F32 gain) { mGain = llclamp(gain, 0.f, 1.f); } - - const LLUUID &getID() const { return mID; } - bool isDone() const; - bool isMuted() const { return mSourceMuted; } - - LLAudioData *getCurrentData(); - LLAudioData *getQueuedData(); - LLAudioBuffer *getCurrentBuffer(); - - bool setupChannel(); - - // Stop the audio source, reset audio id even if muted - void stop(); - - // Start the audio source playing, - // takes mute into account to preserve previous id if nessesary - bool play(const LLUUID &audio_id); - - bool hasPendingPreloads() const; // Has preloads that haven't been done yet - - friend class LLAudioEngine; - friend class LLAudioChannel; -protected: - void setChannel(LLAudioChannel *channelp); - LLAudioChannel *getChannel() const { return mChannelp; } - -protected: - LLUUID mID; // The ID of the source is that of the object if it's attached to an object. - LLUUID mOwnerID; // owner of the object playing the sound - F32 mPriority; - F32 mGain; - bool mSourceMuted; - bool mForcedPriority; // ignore mute, set high priority, researved for sound preview and UI - bool mLoop; - bool mSyncMaster; - bool mSyncSlave; - bool mQueueSounds; - bool mPlayedOnce; - bool mCorrupted; - S32 mType; - LLVector3d mPositionGlobal; - LLVector3 mVelocity; - - //LLAudioSource *mSyncMasterp; // If we're a slave, the source that we're synced to. - LLAudioChannel *mChannelp; // If we're currently playing back, this is the channel that we're assigned to. - LLAudioData *mCurrentDatap; - LLAudioData *mQueuedDatap; - - typedef std::map<LLUUID, LLAudioData *> data_map; - data_map mPreloadMap; - - LLFrameTimer mAgeTimer; -}; - - - - -// -// Generic metadata about a particular piece of audio data. -// The actual data is handled by the derived LLAudioBuffer classes which are -// derived for each audio engine. -// - - -class LLAudioData -{ - public: - LLAudioData(const LLUUID &uuid); - bool load(); - - LLUUID getID() const { return mID; } - LLAudioBuffer *getBuffer() const { return mBufferp; } - - bool hasLocalData() const { return mHasLocalData; } - bool hasDecodedData() const { return mHasDecodedData; } - bool hasCompletedDecode() const { return mHasCompletedDecode; } - bool hasDecodeFailed() const { return mHasDecodeFailed; } - bool hasWAVLoadFailed() const { return mHasWAVLoadFailed; } - - void setHasLocalData(const bool hld) { mHasLocalData = hld; } - void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; } - void setHasCompletedDecode(const bool hcd) { mHasCompletedDecode = hcd; } - void setHasDecodeFailed(const bool hdf) { mHasDecodeFailed = hdf; } - void setHasWAVLoadFailed(const bool hwlf) { mHasWAVLoadFailed = hwlf; } - - friend class LLAudioEngine; // Severe laziness, bad. - - protected: - LLUUID mID; - LLAudioBuffer *mBufferp; // If this data is being used by the audio system, a pointer to the buffer will be set here. - bool mHasLocalData; // Set true if the encoded sound asset file is available locally - bool mHasDecodedData; // Set true if the decoded sound file is available on disk - bool mHasCompletedDecode; // Set true when the sound is decoded - bool mHasDecodeFailed; // Set true if decoding failed, meaning the sound asset is bad - bool mHasWAVLoadFailed; // Set true if loading the decoded WAV file failed, meaning the sound asset should be decoded instead if - // possible -}; - - -// -// Base class for an audio channel, i.e. a channel which is capable of playing back a sound. -// Management of channels is done generically, methods for actually manipulating the channel -// are derived for each audio engine. -// - - -class LLAudioChannel -{ -public: - LLAudioChannel(); - virtual ~LLAudioChannel(); - - virtual void setSource(LLAudioSource *sourcep); - LLAudioSource *getSource() const { return mCurrentSourcep; } - - void setSecondaryGain(F32 gain) { mSecondaryGain = gain; } - F32 getSecondaryGain() { return mSecondaryGain; } - - friend class LLAudioEngine; - friend class LLAudioSource; -protected: - virtual void play() = 0; - virtual void playSynced(LLAudioChannel *channelp) = 0; - virtual void cleanup() = 0; - virtual bool isPlaying() = 0; - void setWaiting(const bool waiting) { mWaiting = waiting; } - bool isWaiting() const { return mWaiting; } - - virtual bool updateBuffer(); // Check to see if the buffer associated with the source changed, and update if necessary. - virtual void update3DPosition() = 0; - virtual void updateLoop() = 0; // Update your loop/completion status, for use by queueing/syncing. -protected: - LLAudioSource *mCurrentSourcep; - LLAudioBuffer *mCurrentBufferp; - bool mLoopedThisFrame; - bool mWaiting; // Waiting for sync. - F32 mSecondaryGain; -}; - - - - -// Basically an interface class to the engine-specific implementation -// of audio data that's ready for playback. -// Will likely get more complex as we decide to do stuff like real streaming audio. - - -class LLAudioBuffer -{ -public: - virtual ~LLAudioBuffer() {}; - virtual bool loadWAV(const std::string& filename) = 0; - virtual U32 getLength() = 0; - - friend class LLAudioEngine; - friend class LLAudioChannel; - friend class LLAudioData; -protected: - bool mInUse; - LLAudioData *mAudioDatap; - LLFrameTimer mLastUseTimer; -}; - -struct SoundData -{ - LLUUID audio_uuid; - LLUUID owner_id; - F32 gain; - S32 type; - LLVector3d pos_global; - - SoundData(const LLUUID &audio_uuid, - const LLUUID& owner_id, - const F32 gain, - const S32 type = LLAudioEngine::AUDIO_TYPE_NONE, - const LLVector3d &pos_global = LLVector3d::zero) : - audio_uuid(audio_uuid), - owner_id(owner_id), - gain(gain), - type(type), - pos_global(pos_global) - { - } -}; - - -extern LLAudioEngine* gAudiop; - -#endif +/**
+ * @file audioengine.h
+ * @brief Definition of LLAudioEngine base class abstracting the 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$
+ */
+
+
+#ifndef LL_AUDIOENGINE_H
+#define LL_AUDIOENGINE_H
+
+#include <list>
+#include <map>
+#include <array>
+
+#include "v3math.h"
+#include "v3dmath.h"
+#include "lltimer.h"
+#include "lluuid.h"
+#include "llframetimer.h"
+#include "llassettype.h"
+#include "llextendedstatus.h"
+
+#include "lllistener.h"
+
+const F32 LL_WIND_UPDATE_INTERVAL = 0.1f;
+const F32 LL_WIND_UNDERWATER_CENTER_FREQ = 20.f;
+
+const F32 ATTACHED_OBJECT_TIMEOUT = 5.0f;
+const F32 DEFAULT_MIN_DISTANCE = 2.0f;
+
+#define LL_MAX_AUDIO_CHANNELS 30
+#define LL_MAX_AUDIO_BUFFERS 40 // Some extra for preloading, maybe?
+
+class LLAudioSource;
+class LLAudioData;
+class LLAudioChannel;
+class LLAudioChannelOpenAL;
+class LLAudioBuffer;
+class LLStreamingAudioInterface;
+struct SoundData;
+
+//
+// LLAudioEngine definition
+//
+class LLAudioEngine
+{
+ friend class LLAudioChannelOpenAL; // bleh. channel needs some listener methods.
+
+public:
+ enum LLAudioType
+ {
+ AUDIO_TYPE_NONE = 0,
+ AUDIO_TYPE_SFX = 1,
+ AUDIO_TYPE_UI = 2,
+ AUDIO_TYPE_AMBIENT = 3,
+ AUDIO_TYPE_COUNT = 4 // last
+ };
+
+ enum LLAudioPlayState
+ {
+ // isInternetStreamPlaying() returns an *S32*, with
+ // 0 = stopped, 1 = playing, 2 = paused.
+ AUDIO_STOPPED = 0,
+ AUDIO_PLAYING = 1,
+ AUDIO_PAUSED = 2
+ };
+
+ LLAudioEngine();
+ virtual ~LLAudioEngine();
+
+ // initialization/startup/shutdown
+ virtual bool init(void *userdata, const std::string &app_title);
+ virtual std::string getDriverName(bool verbose) = 0;
+ virtual LLStreamingAudioInterface *createDefaultStreamingAudioImpl() const = 0;
+ virtual void shutdown();
+
+ // Used by the mechanics of the engine
+ //virtual void processQueue(const LLUUID &sound_guid);
+ virtual void setListener(LLVector3 pos,LLVector3 vel,LLVector3 up,LLVector3 at);
+ virtual void updateWind(LLVector3 direction, F32 camera_height_above_water) = 0;
+ virtual void idle();
+ virtual void updateChannels();
+
+ //
+ // "End user" functionality
+ //
+ virtual bool isWindEnabled();
+ virtual void enableWind(bool state_b);
+
+ // Use these for temporarily muting the audio system.
+ // Does not change buffers, initialization, etc. but
+ // stops playing new sounds.
+ void setMuted(bool muted);
+ bool getMuted() const { return mMuted; }
+#ifdef USE_PLUGIN_MEDIA
+ LLPluginClassMedia* initializeMedia(const std::string& media_type);
+#endif
+ F32 getMasterGain();
+ void setMasterGain(F32 gain);
+
+ F32 getSecondaryGain(S32 type);
+ void setSecondaryGain(S32 type, F32 gain);
+
+ F32 getInternetStreamGain();
+
+ virtual void setDopplerFactor(F32 factor);
+ virtual F32 getDopplerFactor();
+ virtual void setRolloffFactor(F32 factor);
+ virtual F32 getRolloffFactor();
+ virtual void setMaxWindGain(F32 gain);
+
+
+ // Methods actually related to setting up and removing sounds
+ // Owner ID is the owner of the object making the request
+ void triggerSound(const LLUUID &sound_id, const LLUUID& owner_id, const F32 gain,
+ const S32 type = LLAudioEngine::AUDIO_TYPE_NONE,
+ const LLVector3d &pos_global = LLVector3d::zero);
+ void triggerSound(SoundData& soundData);
+
+ bool preloadSound(const LLUUID &id);
+
+ void addAudioSource(LLAudioSource *asp);
+ void cleanupAudioSource(LLAudioSource *asp);
+
+ LLAudioSource *findAudioSource(const LLUUID &source_id);
+ LLAudioData *getAudioData(const LLUUID &audio_uuid);
+
+ // Internet stream implementation manipulation
+ LLStreamingAudioInterface *getStreamingAudioImpl();
+ void setStreamingAudioImpl(LLStreamingAudioInterface *impl);
+ // Internet stream methods - these will call down into the *mStreamingAudioImpl if it exists
+ void startInternetStream(const std::string& url);
+ void stopInternetStream();
+ void pauseInternetStream(S32 pause);
+ void updateInternetStream(); // expected to be called often
+ LLAudioPlayState isInternetStreamPlaying();
+ // use a value from 0.0 to 1.0, inclusive
+ void setInternetStreamGain(F32 vol);
+ std::string getInternetStreamURL();
+
+ // For debugging usage
+ virtual LLVector3 getListenerPos();
+
+ LLAudioBuffer *getFreeBuffer(); // Get a free buffer, or flush an existing one if you have to.
+ LLAudioChannel *getFreeChannel(const F32 priority); // Get a free channel or flush an existing one if your priority is higher
+ void cleanupBuffer(LLAudioBuffer *bufferp);
+
+ bool hasDecodedFile(const LLUUID &uuid);
+ bool hasLocalFile(const LLUUID &uuid);
+
+ bool updateBufferForData(LLAudioData *adp, const LLUUID &audio_uuid = LLUUID::null);
+
+
+ // Asset callback when we're retrieved a sound from the asset server.
+ void startNextTransfer();
+ static void assetCallback(const LLUUID &uuid, LLAssetType::EType type, void *user_data, S32 result_code, LLExtStat ext_status);
+
+ friend class LLPipeline; // For debugging
+public:
+ F32 mMaxWindGain; // Hack. Public to set before fade in?
+
+protected:
+ virtual LLAudioBuffer *createBuffer() = 0;
+ virtual LLAudioChannel *createChannel() = 0;
+
+ virtual bool initWind() = 0;
+ virtual void cleanupWind() = 0;
+ virtual void setInternalGain(F32 gain) = 0;
+
+ void commitDeferredChanges();
+
+ virtual void allocateListener() = 0;
+
+
+ // listener methods
+ virtual void setListenerPos(LLVector3 vec);
+ virtual void setListenerVelocity(LLVector3 vec);
+ virtual void orientListener(LLVector3 up, LLVector3 at);
+ virtual void translateListener(LLVector3 vec);
+
+
+ F64 mapWindVecToGain(LLVector3 wind_vec);
+ F64 mapWindVecToPitch(LLVector3 wind_vec);
+ F64 mapWindVecToPan(LLVector3 wind_vec);
+
+protected:
+ LLListener *mListenerp;
+
+ bool mMuted;
+ void* mUserData;
+
+ S32 mLastStatus;
+
+ bool mEnableWind;
+
+ LLUUID mCurrentTransfer; // Audio file currently being transferred by the system
+ LLFrameTimer mCurrentTransferTimer;
+
+ // A list of all audio sources that are known to the viewer at this time.
+ // This is most likely a superset of the ones that we actually have audio
+ // data for, or are playing back.
+ typedef std::map<LLUUID, LLAudioSource *> source_map;
+ typedef std::map<LLUUID, LLAudioData *> data_map;
+
+ source_map mAllSources;
+ data_map mAllData;
+
+ std::array<LLAudioChannel*, LL_MAX_AUDIO_CHANNELS> mChannels;
+
+ // Buffers needs to change into a different data structure, as the number of buffers
+ // that we have active should be limited by RAM usage, not count.
+ std::array<LLAudioBuffer*, LL_MAX_AUDIO_BUFFERS> mBuffers;
+
+ F32 mMasterGain;
+ F32 mInternalGain; // Actual gain set; either mMasterGain or 0 when mMuted is true.
+ F32 mSecondaryGain[AUDIO_TYPE_COUNT];
+
+ F32 mNextWindUpdate;
+
+ LLFrameTimer mWindUpdateTimer;
+
+private:
+ void setDefaults();
+ LLStreamingAudioInterface *mStreamingAudioImpl;
+};
+
+
+
+
+//
+// Standard audio source. Can be derived from for special sources, such as those attached to objects.
+//
+
+
+class LLAudioSource
+{
+public:
+ // owner_id is the id of the agent responsible for making this sound
+ // play, for example, the owner of the object currently playing it
+ LLAudioSource(const LLUUID &id, const LLUUID& owner_id, const F32 gain, const S32 type = LLAudioEngine::AUDIO_TYPE_NONE);
+ virtual ~LLAudioSource();
+
+ virtual void update(); // Update this audio source
+ void updatePriority();
+
+ void preload(const LLUUID &audio_id); // Only used for preloading UI sounds, now.
+
+ void addAudioData(LLAudioData *adp, bool set_current = true);
+
+ void setForcedPriority(const bool ambient) { mForcedPriority = ambient; }
+ bool isForcedPriority() const { return mForcedPriority; }
+
+ void setLoop(const bool loop) { mLoop = loop; }
+ bool isLoop() const { return mLoop; }
+
+ void setSyncMaster(const bool master) { mSyncMaster = master; }
+ bool isSyncMaster() const { return mSyncMaster; }
+
+ void setSyncSlave(const bool slave) { mSyncSlave = slave; }
+ bool isSyncSlave() const { return mSyncSlave; }
+
+ void setQueueSounds(const bool queue) { mQueueSounds = queue; }
+ bool isQueueSounds() const { return mQueueSounds; }
+
+ void setPlayedOnce(const bool played_once) { mPlayedOnce = played_once; }
+
+ void setType(S32 type) { mType = type; }
+ S32 getType() { return mType; }
+
+ void setPositionGlobal(const LLVector3d &position_global) { mPositionGlobal = position_global; }
+ LLVector3d getPositionGlobal() const { return mPositionGlobal; }
+ LLVector3 getVelocity() const { return mVelocity; }
+ F32 getPriority() const { return mPriority; }
+
+ // Gain should always be clamped between 0 and 1.
+ F32 getGain() const { return mGain; }
+ virtual void setGain(const F32 gain) { mGain = llclamp(gain, 0.f, 1.f); }
+
+ const LLUUID &getID() const { return mID; }
+ bool isDone() const;
+ bool isMuted() const { return mSourceMuted; }
+
+ LLAudioData *getCurrentData();
+ LLAudioData *getQueuedData();
+ LLAudioBuffer *getCurrentBuffer();
+
+ bool setupChannel();
+
+ // Stop the audio source, reset audio id even if muted
+ void stop();
+
+ // Start the audio source playing,
+ // takes mute into account to preserve previous id if nessesary
+ bool play(const LLUUID &audio_id);
+
+ bool hasPendingPreloads() const; // Has preloads that haven't been done yet
+
+ friend class LLAudioEngine;
+ friend class LLAudioChannel;
+protected:
+ void setChannel(LLAudioChannel *channelp);
+ LLAudioChannel *getChannel() const { return mChannelp; }
+
+protected:
+ LLUUID mID; // The ID of the source is that of the object if it's attached to an object.
+ LLUUID mOwnerID; // owner of the object playing the sound
+ F32 mPriority;
+ F32 mGain;
+ bool mSourceMuted;
+ bool mForcedPriority; // ignore mute, set high priority, researved for sound preview and UI
+ bool mLoop;
+ bool mSyncMaster;
+ bool mSyncSlave;
+ bool mQueueSounds;
+ bool mPlayedOnce;
+ bool mCorrupted;
+ S32 mType;
+ LLVector3d mPositionGlobal;
+ LLVector3 mVelocity;
+
+ //LLAudioSource *mSyncMasterp; // If we're a slave, the source that we're synced to.
+ LLAudioChannel *mChannelp; // If we're currently playing back, this is the channel that we're assigned to.
+ LLAudioData *mCurrentDatap;
+ LLAudioData *mQueuedDatap;
+
+ typedef std::map<LLUUID, LLAudioData *> data_map;
+ data_map mPreloadMap;
+
+ LLFrameTimer mAgeTimer;
+};
+
+
+
+
+//
+// Generic metadata about a particular piece of audio data.
+// The actual data is handled by the derived LLAudioBuffer classes which are
+// derived for each audio engine.
+//
+
+
+class LLAudioData
+{
+ public:
+ LLAudioData(const LLUUID &uuid);
+ bool load();
+
+ LLUUID getID() const { return mID; }
+ LLAudioBuffer *getBuffer() const { return mBufferp; }
+
+ bool hasLocalData() const { return mHasLocalData; }
+ bool hasDecodedData() const { return mHasDecodedData; }
+ bool hasCompletedDecode() const { return mHasCompletedDecode; }
+ bool hasDecodeFailed() const { return mHasDecodeFailed; }
+ bool hasWAVLoadFailed() const { return mHasWAVLoadFailed; }
+
+ void setHasLocalData(const bool hld) { mHasLocalData = hld; }
+ void setHasDecodedData(const bool hdd) { mHasDecodedData = hdd; }
+ void setHasCompletedDecode(const bool hcd) { mHasCompletedDecode = hcd; }
+ void setHasDecodeFailed(const bool hdf) { mHasDecodeFailed = hdf; }
+ void setHasWAVLoadFailed(const bool hwlf) { mHasWAVLoadFailed = hwlf; }
+
+ friend class LLAudioEngine; // Severe laziness, bad.
+
+ protected:
+ LLUUID mID;
+ LLAudioBuffer *mBufferp; // If this data is being used by the audio system, a pointer to the buffer will be set here.
+ bool mHasLocalData; // Set true if the encoded sound asset file is available locally
+ bool mHasDecodedData; // Set true if the decoded sound file is available on disk
+ bool mHasCompletedDecode; // Set true when the sound is decoded
+ bool mHasDecodeFailed; // Set true if decoding failed, meaning the sound asset is bad
+ bool mHasWAVLoadFailed; // Set true if loading the decoded WAV file failed, meaning the sound asset should be decoded instead if
+ // possible
+};
+
+
+//
+// Base class for an audio channel, i.e. a channel which is capable of playing back a sound.
+// Management of channels is done generically, methods for actually manipulating the channel
+// are derived for each audio engine.
+//
+
+
+class LLAudioChannel
+{
+public:
+ LLAudioChannel();
+ virtual ~LLAudioChannel();
+
+ virtual void setSource(LLAudioSource *sourcep);
+ LLAudioSource *getSource() const { return mCurrentSourcep; }
+
+ void setSecondaryGain(F32 gain) { mSecondaryGain = gain; }
+ F32 getSecondaryGain() { return mSecondaryGain; }
+
+ friend class LLAudioEngine;
+ friend class LLAudioSource;
+protected:
+ virtual void play() = 0;
+ virtual void playSynced(LLAudioChannel *channelp) = 0;
+ virtual void cleanup() = 0;
+ virtual bool isPlaying() = 0;
+ void setWaiting(const bool waiting) { mWaiting = waiting; }
+ bool isWaiting() const { return mWaiting; }
+
+ virtual bool updateBuffer(); // Check to see if the buffer associated with the source changed, and update if necessary.
+ virtual void update3DPosition() = 0;
+ virtual void updateLoop() = 0; // Update your loop/completion status, for use by queueing/syncing.
+protected:
+ LLAudioSource *mCurrentSourcep;
+ LLAudioBuffer *mCurrentBufferp;
+ bool mLoopedThisFrame;
+ bool mWaiting; // Waiting for sync.
+ F32 mSecondaryGain;
+};
+
+
+
+
+// Basically an interface class to the engine-specific implementation
+// of audio data that's ready for playback.
+// Will likely get more complex as we decide to do stuff like real streaming audio.
+
+
+class LLAudioBuffer
+{
+public:
+ virtual ~LLAudioBuffer() {};
+ virtual bool loadWAV(const std::string& filename) = 0;
+ virtual U32 getLength() = 0;
+
+ friend class LLAudioEngine;
+ friend class LLAudioChannel;
+ friend class LLAudioData;
+protected:
+ bool mInUse;
+ LLAudioData *mAudioDatap;
+ LLFrameTimer mLastUseTimer;
+};
+
+struct SoundData
+{
+ LLUUID audio_uuid;
+ LLUUID owner_id;
+ F32 gain;
+ S32 type;
+ LLVector3d pos_global;
+
+ SoundData(const LLUUID &audio_uuid,
+ const LLUUID& owner_id,
+ const F32 gain,
+ const S32 type = LLAudioEngine::AUDIO_TYPE_NONE,
+ const LLVector3d &pos_global = LLVector3d::zero) :
+ audio_uuid(audio_uuid),
+ owner_id(owner_id),
+ gain(gain),
+ type(type),
+ pos_global(pos_global)
+ {
+ }
+};
+
+
+extern LLAudioEngine* gAudiop;
+
+#endif
diff --git a/indra/llaudio/llaudioengine_fmodstudio.cpp b/indra/llaudio/llaudioengine_fmodstudio.cpp deleted file mode 100644 index f9e2855b82..0000000000 --- a/indra/llaudio/llaudioengine_fmodstudio.cpp +++ /dev/null @@ -1,756 +0,0 @@ -/** - * @file audioengine_fmodstudio.cpp - * @brief Implementation of LLAudioEngine class abstracting the audio - * support as a FMODSTUDIO implementation - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, 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 "llstreamingaudio.h" -#include "llstreamingaudio_fmodstudio.h" - -#include "llaudioengine_fmodstudio.h" -#include "lllistener_fmodstudio.h" - -#include "llerror.h" -#include "llmath.h" -#include "llrand.h" - -#include "fmodstudio/fmod.hpp" -#include "fmodstudio/fmod_errors.h" -#include "lldir.h" -#include "llapr.h" - -#include "sound_ids.h" - -FMOD_RESULT F_CALLBACK windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels); - -FMOD::ChannelGroup *LLAudioEngine_FMODSTUDIO::mChannelGroups[LLAudioEngine::AUDIO_TYPE_COUNT] = {0}; - -LLAudioEngine_FMODSTUDIO::LLAudioEngine_FMODSTUDIO(bool enable_profiler) -: mInited(false), - mWindGen(NULL), - mWindDSP(NULL), - mSystem(NULL), - mEnableProfiler(enable_profiler), - mWindDSPDesc(NULL) -{ -} - - -LLAudioEngine_FMODSTUDIO::~LLAudioEngine_FMODSTUDIO() -{ - // mWindDSPDesc, mWindGen and mWindDSP get cleaned up on cleanupWind in LLAudioEngine::shutdown() - // mSystem gets cleaned up at shutdown() -} - - -static inline bool Check_FMOD_Error(FMOD_RESULT result, const char *string) -{ - if (result == FMOD_OK) - return false; - LL_DEBUGS("FMOD") << string << " Error: " << FMOD_ErrorString(result) << LL_ENDL; - return true; -} - -bool LLAudioEngine_FMODSTUDIO::init(void* userdata, const std::string &app_title) -{ - U32 version; - FMOD_RESULT result; - - LL_DEBUGS("AppInit") << "LLAudioEngine_FMODSTUDIO::init() initializing FMOD" << LL_ENDL; - - result = FMOD::System_Create(&mSystem); - if (Check_FMOD_Error(result, "FMOD::System_Create")) - return false; - - //will call LLAudioEngine_FMODSTUDIO::allocateListener, which needs a valid mSystem pointer. - LLAudioEngine::init(userdata, app_title); - - result = mSystem->getVersion(&version); - Check_FMOD_Error(result, "FMOD::System::getVersion"); - - if (version < FMOD_VERSION) - { - LL_WARNS("AppInit") << "FMOD Studio version mismatch, actual: " << version - << " expected:" << FMOD_VERSION << LL_ENDL; - } - - // In this case, all sounds, PLUS wind and stream will be software. - result = mSystem->setSoftwareChannels(LL_MAX_AUDIO_CHANNELS + 2); - Check_FMOD_Error(result, "FMOD::System::setSoftwareChannels"); - - FMOD_ADVANCEDSETTINGS settings; - memset(&settings, 0, sizeof(settings)); - settings.cbSize = sizeof(FMOD_ADVANCEDSETTINGS); - settings.resamplerMethod = FMOD_DSP_RESAMPLER_LINEAR; - - result = mSystem->setAdvancedSettings(&settings); - Check_FMOD_Error(result, "FMOD::System::setAdvancedSettings"); - - // FMOD_INIT_THREAD_UNSAFE Disables thread safety for API calls. - // Only use this if FMOD is being called from a single thread, and if Studio API is not being used. - U32 fmod_flags = FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_THREAD_UNSAFE; - if (mEnableProfiler) - { - fmod_flags |= FMOD_INIT_PROFILE_ENABLE; - } - -#if LL_LINUX - bool audio_ok = false; - - if (!audio_ok) - { - const char* env_string = getenv("LL_BAD_FMOD_PULSEAUDIO"); - if (NULL == env_string) - { - LL_DEBUGS("AppInit") << "Trying PulseAudio audio output..." << LL_ENDL; - if (mSystem->setOutput(FMOD_OUTPUTTYPE_PULSEAUDIO) == FMOD_OK && - (result = mSystem->init(LL_MAX_AUDIO_CHANNELS + 2, fmod_flags, const_cast<char*>(app_title.c_str()))) == FMOD_OK) - { - LL_DEBUGS("AppInit") << "PulseAudio output initialized OKAY" << LL_ENDL; - audio_ok = true; - } - else - { - Check_FMOD_Error(result, "PulseAudio audio output FAILED to initialize"); - } - } - else - { - LL_DEBUGS("AppInit") << "PulseAudio audio output SKIPPED" << LL_ENDL; - } - } - if (!audio_ok) - { - const char* env_string = getenv("LL_BAD_FMOD_ALSA"); - if (NULL == env_string) - { - LL_DEBUGS("AppInit") << "Trying ALSA audio output..." << LL_ENDL; - if (mSystem->setOutput(FMOD_OUTPUTTYPE_ALSA) == FMOD_OK && - (result = mSystem->init(LL_MAX_AUDIO_CHANNELS + 2, fmod_flags, 0)) == FMOD_OK) - { - LL_DEBUGS("AppInit") << "ALSA audio output initialized OKAY" << LL_ENDL; - audio_ok = true; - } - else - { - Check_FMOD_Error(result, "ALSA audio output FAILED to initialize"); - } - } - else - { - LL_DEBUGS("AppInit") << "ALSA audio output SKIPPED" << LL_ENDL; - } - } - if (!audio_ok) - { - LL_WARNS("AppInit") << "Overall audio init failure." << LL_ENDL; - return false; - } - - // We're interested in logging which output method we - // ended up with, for QA purposes. - FMOD_OUTPUTTYPE output_type; - mSystem->getOutput(&output_type); - switch (output_type) - { - case FMOD_OUTPUTTYPE_NOSOUND: - LL_INFOS("AppInit") << "Audio output: NoSound" << LL_ENDL; break; - case FMOD_OUTPUTTYPE_PULSEAUDIO: - LL_INFOS("AppInit") << "Audio output: PulseAudio" << LL_ENDL; break; - case FMOD_OUTPUTTYPE_ALSA: - LL_INFOS("AppInit") << "Audio output: ALSA" << LL_ENDL; break; - default: - LL_INFOS("AppInit") << "Audio output: Unknown!" << LL_ENDL; break; - }; -#else // LL_LINUX - - // initialize the FMOD engine - // number of channel in this case looks to be identiacal to number of max simultaneously - // playing objects and we can set practically any number - result = mSystem->init(LL_MAX_AUDIO_CHANNELS + 2, fmod_flags, 0); - if (Check_FMOD_Error(result, "Error initializing FMOD Studio with default settins, retrying with other format")) - { - result = mSystem->setSoftwareFormat(44100, FMOD_SPEAKERMODE_STEREO, 0/*- ignore*/); - if (Check_FMOD_Error(result, "Error setting sotware format. Can't init.")) - { - return false; - } - result = mSystem->init(LL_MAX_AUDIO_CHANNELS + 2, fmod_flags, 0); - } - if (Check_FMOD_Error(result, "Error initializing FMOD Studio")) - { - // If it fails here and (result == FMOD_ERR_OUTPUT_CREATEBUFFER), - // we can retry with other settings - return false; - } -#endif - - LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init() FMOD Studio initialized correctly" << LL_ENDL; - - int r_numbuffers, r_samplerate, r_channels; - unsigned int r_bufferlength; - char r_name[512]; - int latency = 100; - mSystem->getDSPBufferSize(&r_bufferlength, &r_numbuffers); - LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_bufferlength=" << r_bufferlength << " bytes" << LL_ENDL; - LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_numbuffers=" << r_numbuffers << LL_ENDL; - - mSystem->getDriverInfo(0, r_name, 511, NULL, &r_samplerate, NULL, &r_channels); - r_name[511] = '\0'; - LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_name=\"" << r_name << "\"" << LL_ENDL; - - if (r_samplerate != 0) - latency = (int)(1000.0f * r_bufferlength * r_numbuffers / r_samplerate); - LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): latency=" << latency << "ms" << LL_ENDL; - - mInited = true; - - LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): initialization complete." << LL_ENDL; - - return true; -} - - -std::string LLAudioEngine_FMODSTUDIO::getDriverName(bool verbose) -{ - llassert_always(mSystem); - if (verbose) - { - U32 version; - if (!Check_FMOD_Error(mSystem->getVersion(&version), "FMOD::System::getVersion")) - { - return llformat("FMOD Studio %1x.%02x.%02x", version >> 16, version >> 8 & 0x000000FF, version & 0x000000FF); - } - } - return "FMOD STUDIO"; -} - - -// create our favourite FMOD-native streaming audio implementation -LLStreamingAudioInterface *LLAudioEngine_FMODSTUDIO::createDefaultStreamingAudioImpl() const -{ - return new LLStreamingAudio_FMODSTUDIO(mSystem); -} - - -void LLAudioEngine_FMODSTUDIO::allocateListener(void) -{ - mListenerp = (LLListener *) new LLListener_FMODSTUDIO(mSystem); - if (!mListenerp) - { - LL_WARNS("FMOD") << "Listener creation failed" << LL_ENDL; - } -} - - -void LLAudioEngine_FMODSTUDIO::shutdown() -{ - stopInternetStream(); - - LL_INFOS("FMOD") << "About to LLAudioEngine::shutdown()" << LL_ENDL; - LLAudioEngine::shutdown(); - - LL_INFOS("FMOD") << "LLAudioEngine_FMODSTUDIO::shutdown() closing FMOD Studio" << LL_ENDL; - if (mSystem) - { - mSystem->close(); - mSystem->release(); - } - LL_INFOS("FMOD") << "LLAudioEngine_FMODSTUDIO::shutdown() done closing FMOD Studio" << LL_ENDL; - - delete mListenerp; - mListenerp = NULL; -} - - -LLAudioBuffer * LLAudioEngine_FMODSTUDIO::createBuffer() -{ - return new LLAudioBufferFMODSTUDIO(mSystem); -} - - -LLAudioChannel * LLAudioEngine_FMODSTUDIO::createChannel() -{ - return new LLAudioChannelFMODSTUDIO(mSystem); -} - -bool LLAudioEngine_FMODSTUDIO::initWind() -{ - mNextWindUpdate = 0.0; - - if (!mWindDSPDesc) - { - mWindDSPDesc = new FMOD_DSP_DESCRIPTION(); - } - - if (!mWindDSP) - { - memset(mWindDSPDesc, 0, sizeof(*mWindDSPDesc)); //Set everything to zero - strncpy(mWindDSPDesc->name, "Wind Unit", sizeof(mWindDSPDesc->name)); - mWindDSPDesc->pluginsdkversion = FMOD_PLUGIN_SDK_VERSION; - mWindDSPDesc->read = &windCallback; // Assign callback - may be called from arbitrary threads - if (Check_FMOD_Error(mSystem->createDSP(mWindDSPDesc, &mWindDSP), "FMOD::createDSP")) - return false; - - if (mWindGen) - delete mWindGen; - - int frequency = 44100; - - FMOD_SPEAKERMODE mode; - if (Check_FMOD_Error(mSystem->getSoftwareFormat(&frequency, &mode, nullptr), "FMOD::System::getSoftwareFormat")) - { - cleanupWind(); - return false; - } - - mWindGen = new LLWindGen<MIXBUFFERFORMAT>((U32)frequency); - - if (Check_FMOD_Error(mWindDSP->setUserData((void*)mWindGen), "FMOD::DSP::setUserData")) - { - cleanupWind(); - return false; - } - if (Check_FMOD_Error(mWindDSP->setChannelFormat(FMOD_CHANNELMASK_STEREO, 2, mode), "FMOD::DSP::setChannelFormat")) - { - cleanupWind(); - return false; - } - } - - // *TODO: Should this guard against multiple plays? - if (Check_FMOD_Error(mSystem->playDSP(mWindDSP, nullptr, false, nullptr), "FMOD::System::playDSP")) - { - cleanupWind(); - return false; - } - return true; -} - - -void LLAudioEngine_FMODSTUDIO::cleanupWind() -{ - if (mWindDSP) - { - FMOD::ChannelGroup* master_group = NULL; - if (!Check_FMOD_Error(mSystem->getMasterChannelGroup(&master_group), "FMOD::System::getMasterChannelGroup") - && master_group) - { - master_group->removeDSP(mWindDSP); - } - mWindDSP->release(); - mWindDSP = NULL; - } - - delete mWindDSPDesc; - mWindDSPDesc = NULL; - - delete mWindGen; - mWindGen = NULL; -} - - -//----------------------------------------------------------------------- -void LLAudioEngine_FMODSTUDIO::updateWind(LLVector3 wind_vec, F32 camera_height_above_water) -{ - LLVector3 wind_pos; - F64 pitch; - F64 center_freq; - - if (!mEnableWind) - { - return; - } - - if (mWindUpdateTimer.checkExpirationAndReset(LL_WIND_UPDATE_INTERVAL)) - { - - // wind comes in as Linden coordinate (+X = forward, +Y = left, +Z = up) - // need to convert this to the conventional orientation DS3D and OpenAL use - // where +X = right, +Y = up, +Z = backwards - - wind_vec.setVec(-wind_vec.mV[1], wind_vec.mV[2], -wind_vec.mV[0]); - - // cerr << "Wind update" << endl; - - pitch = 1.0 + mapWindVecToPitch(wind_vec); - center_freq = 80.0 * pow(pitch, 2.5*(mapWindVecToGain(wind_vec) + 1.0)); - - mWindGen->mTargetFreq = (F32)center_freq; - mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain; - mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec); - } -} - -//----------------------------------------------------------------------- -void LLAudioEngine_FMODSTUDIO::setInternalGain(F32 gain) -{ - if (!mInited) - { - return; - } - - gain = llclamp(gain, 0.0f, 1.0f); - - FMOD::ChannelGroup* master_group = NULL; - if (!Check_FMOD_Error(mSystem->getMasterChannelGroup(&master_group), "FMOD::System::getMasterChannelGroup") - && master_group) - { - master_group->setVolume(gain); - } - - LLStreamingAudioInterface *saimpl = getStreamingAudioImpl(); - if (saimpl) - { - // fmod likes its streaming audio channel gain re-asserted after - // master volume change. - saimpl->setGain(saimpl->getGain()); - } -} - -// -// LLAudioChannelFMODSTUDIO implementation -// - -LLAudioChannelFMODSTUDIO::LLAudioChannelFMODSTUDIO(FMOD::System *system) : LLAudioChannel(), mSystemp(system), mChannelp(NULL), mLastSamplePos(0) -{ -} - - -LLAudioChannelFMODSTUDIO::~LLAudioChannelFMODSTUDIO() -{ - cleanup(); -} - -bool LLAudioChannelFMODSTUDIO::updateBuffer() -{ - if (!mCurrentSourcep) - { - // This channel isn't associated with any source, nothing - // to be updated - return false; - } - - if (LLAudioChannel::updateBuffer()) - { - // Base class update returned true, which means that we need to actually - // set up the channel for a different buffer. - - LLAudioBufferFMODSTUDIO *bufferp = (LLAudioBufferFMODSTUDIO *)mCurrentSourcep->getCurrentBuffer(); - - // Grab the FMOD sample associated with the buffer - FMOD::Sound *soundp = bufferp->getSound(); - if (!soundp) - { - // This is bad, there should ALWAYS be a sound associated with a legit - // buffer. - LL_ERRS() << "No FMOD sound!" << LL_ENDL; - return false; - } - - - // Actually play the sound. Start it off paused so we can do all the necessary - // setup. - if (!mChannelp) - { - FMOD_RESULT result = getSystem()->playSound(soundp, NULL /*free channel?*/, true, &mChannelp); - Check_FMOD_Error(result, "FMOD::System::playSound"); - } - - // Setting up channel mChannelID - } - - // If we have a source for the channel, we need to update its gain. - if (mCurrentSourcep) - { - // SJB: warnings can spam and hurt framerate, disabling - //FMOD_RESULT result; - - mChannelp->setVolume(getSecondaryGain() * mCurrentSourcep->getGain()); - //Check_FMOD_Error(result, "FMOD::Channel::setVolume"); - - mChannelp->setMode(mCurrentSourcep->isLoop() ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF); - /*if(Check_FMOD_Error(result, "FMOD::Channel::setMode")) - { - S32 index; - mChannelp->getIndex(&index); - LL_WARNS() << "Channel " << index << "Source ID: " << mCurrentSourcep->getID() - << " at " << mCurrentSourcep->getPositionGlobal() << LL_ENDL; - }*/ - } - - return true; -} - - -void LLAudioChannelFMODSTUDIO::update3DPosition() -{ - if (!mChannelp) - { - // We're not actually a live channel (i.e., we're not playing back anything) - return; - } - - LLAudioBufferFMODSTUDIO *bufferp = (LLAudioBufferFMODSTUDIO *)mCurrentBufferp; - if (!bufferp) - { - // We don't have a buffer associated with us (should really have been picked up - // by the above if. - return; - } - - if (mCurrentSourcep->isForcedPriority()) - { - // Prioritized UI and preview sounds don't need to do any positional updates. - set3DMode(false); - } - else - { - // Localized sound. Update the position and velocity of the sound. - set3DMode(true); - - LLVector3 float_pos; - float_pos.setVec(mCurrentSourcep->getPositionGlobal()); - FMOD_RESULT result = mChannelp->set3DAttributes((FMOD_VECTOR*)float_pos.mV, (FMOD_VECTOR*)mCurrentSourcep->getVelocity().mV); - Check_FMOD_Error(result, "FMOD::Channel::set3DAttributes"); - } -} - - -void LLAudioChannelFMODSTUDIO::updateLoop() -{ - if (!mChannelp) - { - // May want to clear up the loop/sample counters. - return; - } - - // - // Hack: We keep track of whether we looped or not by seeing when the - // sample position looks like it's going backwards. Not reliable; may - // yield false negatives. - // - U32 cur_pos; - mChannelp->getPosition(&cur_pos, FMOD_TIMEUNIT_PCMBYTES); - - if (cur_pos < (U32)mLastSamplePos) - { - mLoopedThisFrame = true; - } - mLastSamplePos = cur_pos; -} - - -void LLAudioChannelFMODSTUDIO::cleanup() -{ - if (!mChannelp) - { - // Aborting cleanup with no channel handle. - return; - } - - //Cleaning up channel mChannelID - Check_FMOD_Error(mChannelp->stop(), "FMOD::Channel::stop"); - - mCurrentBufferp = NULL; - mChannelp = NULL; -} - - -void LLAudioChannelFMODSTUDIO::play() -{ - if (!mChannelp) - { - LL_WARNS() << "Playing without a channel handle, aborting" << LL_ENDL; - return; - } - - Check_FMOD_Error(mChannelp->setPaused(false), "FMOD::Channel::pause"); - - getSource()->setPlayedOnce(true); - - if (LLAudioEngine_FMODSTUDIO::mChannelGroups[getSource()->getType()]) - mChannelp->setChannelGroup(LLAudioEngine_FMODSTUDIO::mChannelGroups[getSource()->getType()]); -} - - -void LLAudioChannelFMODSTUDIO::playSynced(LLAudioChannel *channelp) -{ - LLAudioChannelFMODSTUDIO *fmod_channelp = (LLAudioChannelFMODSTUDIO*)channelp; - if (!(fmod_channelp->mChannelp && mChannelp)) - { - // Don't have channels allocated to both the master and the slave - return; - } - - U32 cur_pos; - if (Check_FMOD_Error(mChannelp->getPosition(&cur_pos, FMOD_TIMEUNIT_PCMBYTES), "Unable to retrieve current position")) - return; - - cur_pos %= mCurrentBufferp->getLength(); - - // Try to match the position of our sync master - Check_FMOD_Error(mChannelp->setPosition(cur_pos, FMOD_TIMEUNIT_PCMBYTES), "Unable to set current position"); - - // Start us playing - play(); -} - - -bool LLAudioChannelFMODSTUDIO::isPlaying() -{ - if (!mChannelp) - { - return false; - } - - bool paused, playing; - mChannelp->getPaused(&paused); - mChannelp->isPlaying(&playing); - return !paused && playing; -} - - -// -// LLAudioChannelFMODSTUDIO implementation -// - - -LLAudioBufferFMODSTUDIO::LLAudioBufferFMODSTUDIO(FMOD::System *system) : mSystemp(system), mSoundp(NULL) -{ -} - - -LLAudioBufferFMODSTUDIO::~LLAudioBufferFMODSTUDIO() -{ - if (mSoundp) - { - mSoundp->release(); - mSoundp = NULL; - } -} - - -bool LLAudioBufferFMODSTUDIO::loadWAV(const std::string& filename) -{ - // Try to open a wav file from disk. This will eventually go away, as we don't - // really want to block doing this. - if (filename.empty()) - { - // invalid filename, abort. - return false; - } - - if (!gDirUtilp->fileExists(filename)) - { - // File not found, abort. - return false; - } - - if (mSoundp) - { - // If there's already something loaded in this buffer, clean it up. - mSoundp->release(); - mSoundp = NULL; - } - - FMOD_MODE base_mode = FMOD_LOOP_NORMAL; - FMOD_CREATESOUNDEXINFO exinfo; - memset(&exinfo, 0, sizeof(exinfo)); - exinfo.cbsize = sizeof(exinfo); - exinfo.suggestedsoundtype = FMOD_SOUND_TYPE_WAV; //Hint to speed up loading. - // Load up the wav file into an fmod sample (since 1.05 fmod studio expects everything in UTF-8) - FMOD_RESULT result = getSystem()->createSound(filename.c_str(), base_mode, &exinfo, &mSoundp); - - if (result != FMOD_OK) - { - // We failed to load the file for some reason. - LL_WARNS() << "Could not load data '" << filename << "': " << FMOD_ErrorString(result) << LL_ENDL; - - // - // If we EVER want to load wav files provided by end users, we need - // to rethink this! - // - // file is probably corrupt - remove it. - LLFile::remove(filename); - return false; - } - - // Everything went well, return true - return true; -} - - -U32 LLAudioBufferFMODSTUDIO::getLength() -{ - if (!mSoundp) - { - return 0; - } - - U32 length; - mSoundp->getLength(&length, FMOD_TIMEUNIT_PCMBYTES); - return length; -} - - -void LLAudioChannelFMODSTUDIO::set3DMode(bool use3d) -{ - FMOD_MODE current_mode; - if (mChannelp->getMode(¤t_mode) != FMOD_OK) - return; - FMOD_MODE new_mode = current_mode; - new_mode &= ~(use3d ? FMOD_2D : FMOD_3D); - new_mode |= use3d ? FMOD_3D : FMOD_2D; - - if (current_mode != new_mode) - { - mChannelp->setMode(new_mode); - } -} - -// *NOTE: This is almost certainly being called on the mixer thread, -// not the main thread. May have implications for callees or audio -// engine shutdown. - -FMOD_RESULT F_CALLBACK windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels) -{ - // inbuffer = fmod's original mixbuffer. - // outbuffer = the buffer passed from the previous DSP unit. - // length = length in samples at this mix time. - - LLWindGen<LLAudioEngine_FMODSTUDIO::MIXBUFFERFORMAT> *windgen = NULL; - FMOD::DSP *thisdsp = (FMOD::DSP *)dsp_state->instance; - - thisdsp->getUserData((void **)&windgen); - - if (windgen) - { - windgen->windGenerate((LLAudioEngine_FMODSTUDIO::MIXBUFFERFORMAT *)outbuffer, length); - } - - return FMOD_OK; -} diff --git a/indra/llaudio/llaudioengine_fmodstudio.h b/indra/llaudio/llaudioengine_fmodstudio.h deleted file mode 100644 index eb346e0466..0000000000 --- a/indra/llaudio/llaudioengine_fmodstudio.h +++ /dev/null @@ -1,131 +0,0 @@ -/** - * @file audioengine_fmodstudio.h - * @brief Definition of LLAudioEngine class abstracting the audio - * support as a FMODSTUDIO implementation - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, 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$ - */ - -#ifndef LL_AUDIOENGINE_FMODSTUDIO_H -#define LL_AUDIOENGINE_FMODSTUDIO_H - -#include "llaudioengine.h" -#include "llwindgen.h" - -//Stubs -class LLAudioStreamManagerFMODSTUDIO; -namespace FMOD -{ - class System; - class Channel; - class ChannelGroup; - class Sound; - class DSP; -} -typedef struct FMOD_DSP_DESCRIPTION FMOD_DSP_DESCRIPTION; - -//Interfaces -class LLAudioEngine_FMODSTUDIO : public LLAudioEngine -{ -public: - LLAudioEngine_FMODSTUDIO(bool enable_profiler); - virtual ~LLAudioEngine_FMODSTUDIO(); - - // initialization/startup/shutdown - virtual bool init(void *user_data, const std::string &app_title); - virtual std::string getDriverName(bool verbose); - virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const; - virtual void allocateListener(); - - virtual void shutdown(); - - /*virtual*/ bool initWind(); - /*virtual*/ void cleanupWind(); - - /*virtual*/void updateWind(LLVector3 direction, F32 camera_height_above_water); - - typedef F32 MIXBUFFERFORMAT; - - FMOD::System *getSystem() const {return mSystem;} -protected: - /*virtual*/ LLAudioBuffer *createBuffer(); // Get a free buffer, or flush an existing one if you have to. - /*virtual*/ LLAudioChannel *createChannel(); // Create a new audio channel. - - /*virtual*/ void setInternalGain(F32 gain); - - bool mInited; - - LLWindGen<MIXBUFFERFORMAT> *mWindGen; - - FMOD_DSP_DESCRIPTION *mWindDSPDesc; - FMOD::DSP *mWindDSP; - FMOD::System *mSystem; - bool mEnableProfiler; - -public: - static FMOD::ChannelGroup *mChannelGroups[LLAudioEngine::AUDIO_TYPE_COUNT]; -}; - - -class LLAudioChannelFMODSTUDIO : public LLAudioChannel -{ -public: - LLAudioChannelFMODSTUDIO(FMOD::System *audioengine); - virtual ~LLAudioChannelFMODSTUDIO(); - -protected: - /*virtual*/ void play(); - /*virtual*/ void playSynced(LLAudioChannel *channelp); - /*virtual*/ void cleanup(); - /*virtual*/ bool isPlaying(); - - /*virtual*/ bool updateBuffer(); - /*virtual*/ void update3DPosition(); - /*virtual*/ void updateLoop(); - - void set3DMode(bool use3d); -protected: - FMOD::System *getSystem() const {return mSystemp;} - FMOD::System *mSystemp; - FMOD::Channel *mChannelp; - S32 mLastSamplePos; -}; - - -class LLAudioBufferFMODSTUDIO : public LLAudioBuffer -{ -public: - LLAudioBufferFMODSTUDIO(FMOD::System *audioengine); - virtual ~LLAudioBufferFMODSTUDIO(); - - /*virtual*/ bool loadWAV(const std::string& filename); - /*virtual*/ U32 getLength(); - friend class LLAudioChannelFMODSTUDIO; -protected: - FMOD::System *getSystem() const {return mSystemp;} - FMOD::System *mSystemp; - FMOD::Sound *getSound() const{ return mSoundp; } - FMOD::Sound *mSoundp; -}; - - -#endif // LL_AUDIOENGINE_FMODSTUDIO_H diff --git a/indra/llaudio/llaudioengine_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp index 1c4c67e6f6..9e62e9bfec 100644 --- a/indra/llaudio/llaudioengine_openal.cpp +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -1,551 +1,560 @@ -/** - * @file audioengine_openal.cpp - * @brief implementation of audio engine using OpenAL - * support as a OpenAL 3D implementation - * - * $LicenseInfo:firstyear=2002&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 "lldir.h" - -#include "llaudioengine_openal.h" -#include "lllistener_openal.h" - - -const float LLAudioEngine_OpenAL::WIND_BUFFER_SIZE_SEC = 0.05f; - -LLAudioEngine_OpenAL::LLAudioEngine_OpenAL() - : - mWindGen(NULL), - mWindBuf(NULL), - mWindBufFreq(0), - mWindBufSamples(0), - mWindBufBytes(0), - mWindSource(AL_NONE), - mNumEmptyWindALBuffers(MAX_NUM_WIND_BUFFERS) -{ -} - -// virtual -LLAudioEngine_OpenAL::~LLAudioEngine_OpenAL() -{ -} - -// virtual -bool LLAudioEngine_OpenAL::init(void* userdata, const std::string &app_title) -{ - mWindGen = NULL; - LLAudioEngine::init(userdata, app_title); - - if(!alutInit(NULL, NULL)) - { - LL_WARNS() << "LLAudioEngine_OpenAL::init() ALUT initialization failed: " << alutGetErrorString (alutGetError ()) << LL_ENDL; - return false; - } - - LL_INFOS() << "LLAudioEngine_OpenAL::init() OpenAL successfully initialized" << LL_ENDL; - - LL_INFOS() << "OpenAL version: " - << ll_safe_string(alGetString(AL_VERSION)) << LL_ENDL; - LL_INFOS() << "OpenAL vendor: " - << ll_safe_string(alGetString(AL_VENDOR)) << LL_ENDL; - LL_INFOS() << "OpenAL renderer: " - << ll_safe_string(alGetString(AL_RENDERER)) << LL_ENDL; - - ALint major = alutGetMajorVersion (); - ALint minor = alutGetMinorVersion (); - LL_INFOS() << "ALUT version: " << major << "." << minor << LL_ENDL; - - ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext()); - - alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &major); - alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &minor); - LL_INFOS() << "ALC version: " << major << "." << minor << LL_ENDL; - - LL_INFOS() << "ALC default device: " - << ll_safe_string(alcGetString(device, - ALC_DEFAULT_DEVICE_SPECIFIER)) - << LL_ENDL; - - return true; -} - -// virtual -std::string LLAudioEngine_OpenAL::getDriverName(bool verbose) -{ - ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext()); - std::ostringstream version; - - version << - "OpenAL"; - - if (verbose) - { - version << - ", version " << - ll_safe_string(alGetString(AL_VERSION)) << - " / " << - ll_safe_string(alGetString(AL_VENDOR)) << - " / " << - ll_safe_string(alGetString(AL_RENDERER)); - - if (device) - version << - ": " << - ll_safe_string(alcGetString(device, - ALC_DEFAULT_DEVICE_SPECIFIER)); - } - - return version.str(); -} - -// virtual -void LLAudioEngine_OpenAL::allocateListener() -{ - mListenerp = (LLListener *) new LLListener_OpenAL(); - if(!mListenerp) - { - LL_WARNS() << "LLAudioEngine_OpenAL::allocateListener() Listener creation failed" << LL_ENDL; - } -} - -// virtual -void LLAudioEngine_OpenAL::shutdown() -{ - LL_INFOS() << "About to LLAudioEngine::shutdown()" << LL_ENDL; - LLAudioEngine::shutdown(); - - LL_INFOS() << "About to alutExit()" << LL_ENDL; - if(!alutExit()) - { - LL_WARNS() << "Nuts." << LL_ENDL; - LL_WARNS() << "LLAudioEngine_OpenAL::shutdown() ALUT shutdown failed: " << alutGetErrorString (alutGetError ()) << LL_ENDL; - } - - LL_INFOS() << "LLAudioEngine_OpenAL::shutdown() OpenAL successfully shut down" << LL_ENDL; - - delete mListenerp; - mListenerp = NULL; -} - -LLAudioBuffer *LLAudioEngine_OpenAL::createBuffer() -{ - return new LLAudioBufferOpenAL(); -} - -LLAudioChannel *LLAudioEngine_OpenAL::createChannel() -{ - return new LLAudioChannelOpenAL(); -} - -void LLAudioEngine_OpenAL::setInternalGain(F32 gain) -{ - //LL_INFOS() << "LLAudioEngine_OpenAL::setInternalGain() Gain: " << gain << LL_ENDL; - alListenerf(AL_GAIN, gain); -} - -LLAudioChannelOpenAL::LLAudioChannelOpenAL() - : - mALSource(AL_NONE), - mLastSamplePos(0) -{ - alGenSources(1, &mALSource); -} - -LLAudioChannelOpenAL::~LLAudioChannelOpenAL() -{ - cleanup(); - alDeleteSources(1, &mALSource); -} - -void LLAudioChannelOpenAL::cleanup() -{ - alSourceStop(mALSource); - mCurrentBufferp = NULL; -} - -void LLAudioChannelOpenAL::play() -{ - if (mALSource == AL_NONE) - { - LL_WARNS() << "Playing without a mALSource, aborting" << LL_ENDL; - return; - } - - if(!isPlaying()) - { - alSourcePlay(mALSource); - getSource()->setPlayedOnce(true); - } -} - -void LLAudioChannelOpenAL::playSynced(LLAudioChannel *channelp) -{ - if (channelp) - { - LLAudioChannelOpenAL *masterchannelp = - (LLAudioChannelOpenAL*)channelp; - if (mALSource != AL_NONE && - masterchannelp->mALSource != AL_NONE) - { - // we have channels allocated to master and slave - ALfloat master_offset; - alGetSourcef(masterchannelp->mALSource, AL_SEC_OFFSET, - &master_offset); - - LL_INFOS() << "Syncing with master at " << master_offset - << "sec" << LL_ENDL; - // *TODO: detect when this fails, maybe use AL_SAMPLE_ - alSourcef(mALSource, AL_SEC_OFFSET, master_offset); - } - } - play(); -} - -bool LLAudioChannelOpenAL::isPlaying() -{ - if (mALSource != AL_NONE) - { - ALint state; - alGetSourcei(mALSource, AL_SOURCE_STATE, &state); - if(state == AL_PLAYING) - { - return true; - } - } - - return false; -} - -bool LLAudioChannelOpenAL::updateBuffer() -{ - if (!mCurrentSourcep) - { - // This channel isn't associated with any source, nothing - // to be updated - return false; - } - - if (LLAudioChannel::updateBuffer()) - { - // Base class update returned true, which means that we need to actually - // set up the source for a different buffer. - LLAudioBufferOpenAL *bufferp = (LLAudioBufferOpenAL *)mCurrentSourcep->getCurrentBuffer(); - ALuint buffer = bufferp->getBuffer(); - alSourcei(mALSource, AL_BUFFER, buffer); - mLastSamplePos = 0; - } - - if (mCurrentSourcep) - { - alSourcef(mALSource, AL_GAIN, - mCurrentSourcep->getGain() * getSecondaryGain()); - alSourcei(mALSource, AL_LOOPING, - mCurrentSourcep->isLoop() ? AL_TRUE : AL_FALSE); - alSourcef(mALSource, AL_ROLLOFF_FACTOR, - gAudiop->mListenerp->getRolloffFactor()); - } - - return true; -} - - -void LLAudioChannelOpenAL::updateLoop() -{ - if (mALSource == AL_NONE) - { - return; - } - - // Hack: We keep track of whether we looped or not by seeing when the - // sample position looks like it's going backwards. Not reliable; may - // yield false negatives. - // - ALint cur_pos; - alGetSourcei(mALSource, AL_SAMPLE_OFFSET, &cur_pos); - if (cur_pos < mLastSamplePos) - { - mLoopedThisFrame = true; - } - mLastSamplePos = cur_pos; -} - - -void LLAudioChannelOpenAL::update3DPosition() -{ - if(!mCurrentSourcep) - { - return; - } - if (mCurrentSourcep->isForcedPriority()) - { - alSource3f(mALSource, AL_POSITION, 0.0, 0.0, 0.0); - alSource3f(mALSource, AL_VELOCITY, 0.0, 0.0, 0.0); - alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_TRUE); - } else { - LLVector3 float_pos; - float_pos.setVec(mCurrentSourcep->getPositionGlobal()); - alSourcefv(mALSource, AL_POSITION, float_pos.mV); - alSourcefv(mALSource, AL_VELOCITY, mCurrentSourcep->getVelocity().mV); - alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_FALSE); - } - - alSourcef(mALSource, AL_GAIN, mCurrentSourcep->getGain() * getSecondaryGain()); -} - -LLAudioBufferOpenAL::LLAudioBufferOpenAL() -{ - mALBuffer = AL_NONE; -} - -LLAudioBufferOpenAL::~LLAudioBufferOpenAL() -{ - cleanup(); -} - -void LLAudioBufferOpenAL::cleanup() -{ - if(mALBuffer != AL_NONE) - { - alDeleteBuffers(1, &mALBuffer); - mALBuffer = AL_NONE; - } -} - -bool LLAudioBufferOpenAL::loadWAV(const std::string& filename) -{ - cleanup(); - mALBuffer = alutCreateBufferFromFile(filename.c_str()); - if(mALBuffer == AL_NONE) - { - ALenum error = alutGetError(); - if (gDirUtilp->fileExists(filename)) - { - LL_WARNS() << - "LLAudioBufferOpenAL::loadWAV() Error loading " - << filename - << " " << alutGetErrorString(error) << LL_ENDL; - } - else - { - // It's common for the file to not actually exist. - LL_DEBUGS() << - "LLAudioBufferOpenAL::loadWAV() Error loading " - << filename - << " " << alutGetErrorString(error) << LL_ENDL; - } - return false; - } - - return true; -} - -U32 LLAudioBufferOpenAL::getLength() -{ - if(mALBuffer == AL_NONE) - { - return 0; - } - ALint length; - alGetBufferi(mALBuffer, AL_SIZE, &length); - return length / 2; // convert size in bytes to size in (16-bit) samples -} - -// ------------ - -bool LLAudioEngine_OpenAL::initWind() -{ - ALenum error; - LL_INFOS() << "LLAudioEngine_OpenAL::initWind() start" << LL_ENDL; - - mNumEmptyWindALBuffers = MAX_NUM_WIND_BUFFERS; - - alGetError(); /* clear error */ - - alGenSources(1,&mWindSource); - - if((error=alGetError()) != AL_NO_ERROR) - { - LL_WARNS() << "LLAudioEngine_OpenAL::initWind() Error creating wind sources: "<<error<<LL_ENDL; - } - - mWindGen = new LLWindGen<WIND_SAMPLE_T>; - - mWindBufFreq = mWindGen->getInputSamplingRate(); - mWindBufSamples = llceil(mWindBufFreq * WIND_BUFFER_SIZE_SEC); - mWindBufBytes = mWindBufSamples * 2 /*stereo*/ * sizeof(WIND_SAMPLE_T); - - mWindBuf = new WIND_SAMPLE_T [mWindBufSamples * 2 /*stereo*/]; - - if(mWindBuf==NULL) - { - LL_ERRS() << "LLAudioEngine_OpenAL::initWind() Error creating wind memory buffer" << LL_ENDL; - return false; - } - - LL_INFOS() << "LLAudioEngine_OpenAL::initWind() done" << LL_ENDL; - - return true; -} - -void LLAudioEngine_OpenAL::cleanupWind() -{ - LL_INFOS() << "LLAudioEngine_OpenAL::cleanupWind()" << LL_ENDL; - - if (mWindSource != AL_NONE) - { - // detach and delete all outstanding buffers on the wind source - alSourceStop(mWindSource); - ALint processed; - alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed); - while (processed--) - { - ALuint buffer = AL_NONE; - alSourceUnqueueBuffers(mWindSource, 1, &buffer); - alDeleteBuffers(1, &buffer); - } - - // delete the wind source itself - alDeleteSources(1, &mWindSource); - - mWindSource = AL_NONE; - } - - delete[] mWindBuf; - mWindBuf = NULL; - - delete mWindGen; - mWindGen = NULL; -} - -void LLAudioEngine_OpenAL::updateWind(LLVector3 wind_vec, F32 camera_altitude) -{ - LLVector3 wind_pos; - F64 pitch; - F64 center_freq; - ALenum error; - - if (!mEnableWind) - return; - - if(!mWindBuf) - return; - - if (mWindUpdateTimer.checkExpirationAndReset(LL_WIND_UPDATE_INTERVAL)) - { - - // wind comes in as Linden coordinate (+X = forward, +Y = left, +Z = up) - // need to convert this to the conventional orientation DS3D and OpenAL use - // where +X = right, +Y = up, +Z = backwards - - wind_vec.setVec(-wind_vec.mV[1], wind_vec.mV[2], -wind_vec.mV[0]); - - pitch = 1.0 + mapWindVecToPitch(wind_vec); - center_freq = 80.0 * pow(pitch,2.5*(mapWindVecToGain(wind_vec)+1.0)); - - mWindGen->mTargetFreq = (F32)center_freq; - mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain; - mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec); - - alSourcei(mWindSource, AL_LOOPING, AL_FALSE); - alSource3f(mWindSource, AL_POSITION, 0.0, 0.0, 0.0); - alSource3f(mWindSource, AL_VELOCITY, 0.0, 0.0, 0.0); - alSourcef(mWindSource, AL_ROLLOFF_FACTOR, 0.0); - alSourcei(mWindSource, AL_SOURCE_RELATIVE, AL_TRUE); - } - - // ok lets make a wind buffer now - - ALint processed, queued, unprocessed; - alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed); - alGetSourcei(mWindSource, AL_BUFFERS_QUEUED, &queued); - unprocessed = queued - processed; - - // ensure that there are always at least 3x as many filled buffers - // queued as we managed to empty since last time. - mNumEmptyWindALBuffers = llmin(mNumEmptyWindALBuffers + processed * 3 - unprocessed, MAX_NUM_WIND_BUFFERS-unprocessed); - mNumEmptyWindALBuffers = llmax(mNumEmptyWindALBuffers, 0); - - //LL_INFOS() << "mNumEmptyWindALBuffers: " << mNumEmptyWindALBuffers <<" (" << unprocessed << ":" << processed << ")" << LL_ENDL; - - while(processed--) // unqueue old buffers - { - ALuint buffer; - ALenum error; - alGetError(); /* clear error */ - alSourceUnqueueBuffers(mWindSource, 1, &buffer); - error = alGetError(); - if(error != AL_NO_ERROR) - { - LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (unqueuing) buffers" << LL_ENDL; - } - else - { - alDeleteBuffers(1, &buffer); - } - } - - unprocessed += mNumEmptyWindALBuffers; - while (mNumEmptyWindALBuffers > 0) // fill+queue new buffers - { - ALuint buffer; - alGetError(); /* clear error */ - alGenBuffers(1,&buffer); - if((error=alGetError()) != AL_NO_ERROR) - { - LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() Error creating wind buffer: " << error << LL_ENDL; - break; - } - - alBufferData(buffer, - AL_FORMAT_STEREO16, - mWindGen->windGenerate(mWindBuf, - mWindBufSamples), - mWindBufBytes, - mWindBufFreq); - error = alGetError(); - if(error != AL_NO_ERROR) - { - LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (bufferdata) buffers" << LL_ENDL; - } - - alSourceQueueBuffers(mWindSource, 1, &buffer); - error = alGetError(); - if(error != AL_NO_ERROR) - { - LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (queuing) buffers" << LL_ENDL; - } - - --mNumEmptyWindALBuffers; - } - - ALint playing; - alGetSourcei(mWindSource, AL_SOURCE_STATE, &playing); - if(playing != AL_PLAYING) - { - alSourcePlay(mWindSource); - - LL_DEBUGS() << "Wind had stopped - probably ran out of buffers - restarting: " << (unprocessed+mNumEmptyWindALBuffers) << " now queued." << LL_ENDL; - } -} - +/**
+ * @file audioengine_openal.cpp
+ * @brief implementation of audio engine using OpenAL
+ * support as a OpenAL 3D implementation
+ *
+ * $LicenseInfo:firstyear=2002&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 "lldir.h"
+
+#include "llaudioengine_openal.h"
+#include "lllistener_openal.h"
+
+
+const float LLAudioEngine_OpenAL::WIND_BUFFER_SIZE_SEC = 0.05f;
+
+LLAudioEngine_OpenAL::LLAudioEngine_OpenAL()
+ :
+ mWindGen(NULL),
+ mWindBuf(NULL),
+ mWindBufFreq(0),
+ mWindBufSamples(0),
+ mWindBufBytes(0),
+ mWindSource(AL_NONE),
+ mNumEmptyWindALBuffers(MAX_NUM_WIND_BUFFERS)
+{
+}
+
+// virtual
+LLAudioEngine_OpenAL::~LLAudioEngine_OpenAL()
+{
+}
+
+// virtual
+bool LLAudioEngine_OpenAL::init(void* userdata, const std::string &app_title)
+{
+ mWindGen = NULL;
+ LLAudioEngine::init(userdata, app_title);
+
+ if(!alutInit(NULL, NULL))
+ {
+ LL_WARNS() << "LLAudioEngine_OpenAL::init() ALUT initialization failed: " << alutGetErrorString (alutGetError ()) << LL_ENDL;
+ return false;
+ }
+
+ LL_INFOS() << "LLAudioEngine_OpenAL::init() OpenAL successfully initialized" << LL_ENDL;
+
+ LL_INFOS() << "OpenAL version: "
+ << ll_safe_string(alGetString(AL_VERSION)) << LL_ENDL;
+ LL_INFOS() << "OpenAL vendor: "
+ << ll_safe_string(alGetString(AL_VENDOR)) << LL_ENDL;
+ LL_INFOS() << "OpenAL renderer: "
+ << ll_safe_string(alGetString(AL_RENDERER)) << LL_ENDL;
+
+ ALint major = alutGetMajorVersion ();
+ ALint minor = alutGetMinorVersion ();
+ LL_INFOS() << "ALUT version: " << major << "." << minor << LL_ENDL;
+
+ ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
+
+ alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &major);
+ alcGetIntegerv(device, ALC_MAJOR_VERSION, 1, &minor);
+ LL_INFOS() << "ALC version: " << major << "." << minor << LL_ENDL;
+
+ LL_INFOS() << "ALC default device: "
+ << ll_safe_string(alcGetString(device,
+ ALC_DEFAULT_DEVICE_SPECIFIER))
+ << LL_ENDL;
+
+ return true;
+}
+
+// virtual
+std::string LLAudioEngine_OpenAL::getDriverName(bool verbose)
+{
+ ALCdevice *device = alcGetContextsDevice(alcGetCurrentContext());
+ std::ostringstream version;
+
+ version <<
+ "OpenAL";
+
+ if (verbose)
+ {
+ version <<
+ ", version " <<
+ ll_safe_string(alGetString(AL_VERSION)) <<
+ " / " <<
+ ll_safe_string(alGetString(AL_VENDOR)) <<
+ " / " <<
+ ll_safe_string(alGetString(AL_RENDERER));
+
+ if (device)
+ version <<
+ ": " <<
+ ll_safe_string(alcGetString(device,
+ ALC_DEFAULT_DEVICE_SPECIFIER));
+ }
+
+ return version.str();
+}
+
+// virtual
+void LLAudioEngine_OpenAL::allocateListener()
+{
+ mListenerp = (LLListener *) new LLListener_OpenAL();
+ if(!mListenerp)
+ {
+ LL_WARNS() << "LLAudioEngine_OpenAL::allocateListener() Listener creation failed" << LL_ENDL;
+ }
+}
+
+// virtual
+void LLAudioEngine_OpenAL::shutdown()
+{
+ LL_INFOS() << "About to LLAudioEngine::shutdown()" << LL_ENDL;
+ LLAudioEngine::shutdown();
+
+ // If a subsequent error occurs while there is still an error recorded
+ // internally, the second error will simply be ignored.
+ // Clear previous error to make sure we will captuare a valid failure reason
+ ALenum error = alutGetError();
+ if (error != ALUT_ERROR_NO_ERROR)
+ {
+ LL_WARNS() << "Uncleared error state prior to shutdown: "
+ << alutGetErrorString(error) << LL_ENDL;
+ }
+
+ LL_INFOS() << "About to alutExit()" << LL_ENDL;
+ if(!alutExit())
+ {
+ LL_WARNS() << "LLAudioEngine_OpenAL::shutdown() ALUT shutdown failed: " << alutGetErrorString (alutGetError ()) << LL_ENDL;
+ }
+
+ LL_INFOS() << "LLAudioEngine_OpenAL::shutdown() OpenAL successfully shut down" << LL_ENDL;
+
+ delete mListenerp;
+ mListenerp = NULL;
+}
+
+LLAudioBuffer *LLAudioEngine_OpenAL::createBuffer()
+{
+ return new LLAudioBufferOpenAL();
+}
+
+LLAudioChannel *LLAudioEngine_OpenAL::createChannel()
+{
+ return new LLAudioChannelOpenAL();
+}
+
+void LLAudioEngine_OpenAL::setInternalGain(F32 gain)
+{
+ //LL_INFOS() << "LLAudioEngine_OpenAL::setInternalGain() Gain: " << gain << LL_ENDL;
+ alListenerf(AL_GAIN, gain);
+}
+
+LLAudioChannelOpenAL::LLAudioChannelOpenAL()
+ :
+ mALSource(AL_NONE),
+ mLastSamplePos(0)
+{
+ alGenSources(1, &mALSource);
+}
+
+LLAudioChannelOpenAL::~LLAudioChannelOpenAL()
+{
+ cleanup();
+ alDeleteSources(1, &mALSource);
+}
+
+void LLAudioChannelOpenAL::cleanup()
+{
+ alSourceStop(mALSource);
+ mCurrentBufferp = NULL;
+}
+
+void LLAudioChannelOpenAL::play()
+{
+ if (mALSource == AL_NONE)
+ {
+ LL_WARNS() << "Playing without a mALSource, aborting" << LL_ENDL;
+ return;
+ }
+
+ if(!isPlaying())
+ {
+ alSourcePlay(mALSource);
+ getSource()->setPlayedOnce(true);
+ }
+}
+
+void LLAudioChannelOpenAL::playSynced(LLAudioChannel *channelp)
+{
+ if (channelp)
+ {
+ LLAudioChannelOpenAL *masterchannelp =
+ (LLAudioChannelOpenAL*)channelp;
+ if (mALSource != AL_NONE &&
+ masterchannelp->mALSource != AL_NONE)
+ {
+ // we have channels allocated to master and slave
+ ALfloat master_offset;
+ alGetSourcef(masterchannelp->mALSource, AL_SEC_OFFSET,
+ &master_offset);
+
+ LL_INFOS() << "Syncing with master at " << master_offset
+ << "sec" << LL_ENDL;
+ // *TODO: detect when this fails, maybe use AL_SAMPLE_
+ alSourcef(mALSource, AL_SEC_OFFSET, master_offset);
+ }
+ }
+ play();
+}
+
+bool LLAudioChannelOpenAL::isPlaying()
+{
+ if (mALSource != AL_NONE)
+ {
+ ALint state;
+ alGetSourcei(mALSource, AL_SOURCE_STATE, &state);
+ if(state == AL_PLAYING)
+ {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool LLAudioChannelOpenAL::updateBuffer()
+{
+ if (!mCurrentSourcep)
+ {
+ // This channel isn't associated with any source, nothing
+ // to be updated
+ return false;
+ }
+
+ if (LLAudioChannel::updateBuffer())
+ {
+ // Base class update returned true, which means that we need to actually
+ // set up the source for a different buffer.
+ LLAudioBufferOpenAL *bufferp = (LLAudioBufferOpenAL *)mCurrentSourcep->getCurrentBuffer();
+ ALuint buffer = bufferp->getBuffer();
+ alSourcei(mALSource, AL_BUFFER, buffer);
+ mLastSamplePos = 0;
+ }
+
+ if (mCurrentSourcep)
+ {
+ alSourcef(mALSource, AL_GAIN,
+ mCurrentSourcep->getGain() * getSecondaryGain());
+ alSourcei(mALSource, AL_LOOPING,
+ mCurrentSourcep->isLoop() ? AL_TRUE : AL_FALSE);
+ alSourcef(mALSource, AL_ROLLOFF_FACTOR,
+ gAudiop->mListenerp->getRolloffFactor());
+ }
+
+ return true;
+}
+
+
+void LLAudioChannelOpenAL::updateLoop()
+{
+ if (mALSource == AL_NONE)
+ {
+ return;
+ }
+
+ // Hack: We keep track of whether we looped or not by seeing when the
+ // sample position looks like it's going backwards. Not reliable; may
+ // yield false negatives.
+ //
+ ALint cur_pos;
+ alGetSourcei(mALSource, AL_SAMPLE_OFFSET, &cur_pos);
+ if (cur_pos < mLastSamplePos)
+ {
+ mLoopedThisFrame = true;
+ }
+ mLastSamplePos = cur_pos;
+}
+
+
+void LLAudioChannelOpenAL::update3DPosition()
+{
+ if(!mCurrentSourcep)
+ {
+ return;
+ }
+ if (mCurrentSourcep->isForcedPriority())
+ {
+ alSource3f(mALSource, AL_POSITION, 0.0, 0.0, 0.0);
+ alSource3f(mALSource, AL_VELOCITY, 0.0, 0.0, 0.0);
+ alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_TRUE);
+ } else {
+ LLVector3 float_pos;
+ float_pos.setVec(mCurrentSourcep->getPositionGlobal());
+ alSourcefv(mALSource, AL_POSITION, float_pos.mV);
+ alSourcefv(mALSource, AL_VELOCITY, mCurrentSourcep->getVelocity().mV);
+ alSourcei (mALSource, AL_SOURCE_RELATIVE, AL_FALSE);
+ }
+
+ alSourcef(mALSource, AL_GAIN, mCurrentSourcep->getGain() * getSecondaryGain());
+}
+
+LLAudioBufferOpenAL::LLAudioBufferOpenAL()
+{
+ mALBuffer = AL_NONE;
+}
+
+LLAudioBufferOpenAL::~LLAudioBufferOpenAL()
+{
+ cleanup();
+}
+
+void LLAudioBufferOpenAL::cleanup()
+{
+ if(mALBuffer != AL_NONE)
+ {
+ alDeleteBuffers(1, &mALBuffer);
+ mALBuffer = AL_NONE;
+ }
+}
+
+bool LLAudioBufferOpenAL::loadWAV(const std::string& filename)
+{
+ cleanup();
+ mALBuffer = alutCreateBufferFromFile(filename.c_str());
+ if(mALBuffer == AL_NONE)
+ {
+ ALenum error = alutGetError();
+ if (gDirUtilp->fileExists(filename))
+ {
+ LL_WARNS() <<
+ "LLAudioBufferOpenAL::loadWAV() Error loading "
+ << filename
+ << " " << alutGetErrorString(error) << LL_ENDL;
+ }
+ else
+ {
+ // It's common for the file to not actually exist.
+ LL_DEBUGS() <<
+ "LLAudioBufferOpenAL::loadWAV() Error loading "
+ << filename
+ << " " << alutGetErrorString(error) << LL_ENDL;
+ }
+ return false;
+ }
+
+ return true;
+}
+
+U32 LLAudioBufferOpenAL::getLength()
+{
+ if(mALBuffer == AL_NONE)
+ {
+ return 0;
+ }
+ ALint length;
+ alGetBufferi(mALBuffer, AL_SIZE, &length);
+ return length / 2; // convert size in bytes to size in (16-bit) samples
+}
+
+// ------------
+
+bool LLAudioEngine_OpenAL::initWind()
+{
+ ALenum error;
+ LL_INFOS() << "LLAudioEngine_OpenAL::initWind() start" << LL_ENDL;
+
+ mNumEmptyWindALBuffers = MAX_NUM_WIND_BUFFERS;
+
+ alGetError(); /* clear error */
+
+ alGenSources(1,&mWindSource);
+
+ if((error=alGetError()) != AL_NO_ERROR)
+ {
+ LL_WARNS() << "LLAudioEngine_OpenAL::initWind() Error creating wind sources: "<<error<<LL_ENDL;
+ }
+
+ mWindGen = new LLWindGen<WIND_SAMPLE_T>;
+
+ mWindBufFreq = mWindGen->getInputSamplingRate();
+ mWindBufSamples = llceil(mWindBufFreq * WIND_BUFFER_SIZE_SEC);
+ mWindBufBytes = mWindBufSamples * 2 /*stereo*/ * sizeof(WIND_SAMPLE_T);
+
+ mWindBuf = new WIND_SAMPLE_T [mWindBufSamples * 2 /*stereo*/];
+
+ if(mWindBuf==NULL)
+ {
+ LL_ERRS() << "LLAudioEngine_OpenAL::initWind() Error creating wind memory buffer" << LL_ENDL;
+ return false;
+ }
+
+ LL_INFOS() << "LLAudioEngine_OpenAL::initWind() done" << LL_ENDL;
+
+ return true;
+}
+
+void LLAudioEngine_OpenAL::cleanupWind()
+{
+ LL_INFOS() << "LLAudioEngine_OpenAL::cleanupWind()" << LL_ENDL;
+
+ if (mWindSource != AL_NONE)
+ {
+ // detach and delete all outstanding buffers on the wind source
+ alSourceStop(mWindSource);
+ ALint processed;
+ alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed);
+ while (processed--)
+ {
+ ALuint buffer = AL_NONE;
+ alSourceUnqueueBuffers(mWindSource, 1, &buffer);
+ alDeleteBuffers(1, &buffer);
+ }
+
+ // delete the wind source itself
+ alDeleteSources(1, &mWindSource);
+
+ mWindSource = AL_NONE;
+ }
+
+ delete[] mWindBuf;
+ mWindBuf = NULL;
+
+ delete mWindGen;
+ mWindGen = NULL;
+}
+
+void LLAudioEngine_OpenAL::updateWind(LLVector3 wind_vec, F32 camera_altitude)
+{
+ LLVector3 wind_pos;
+ F64 pitch;
+ F64 center_freq;
+ ALenum error;
+
+ if (!mEnableWind)
+ return;
+
+ if(!mWindBuf)
+ return;
+
+ if (mWindUpdateTimer.checkExpirationAndReset(LL_WIND_UPDATE_INTERVAL))
+ {
+
+ // wind comes in as Linden coordinate (+X = forward, +Y = left, +Z = up)
+ // need to convert this to the conventional orientation DS3D and OpenAL use
+ // where +X = right, +Y = up, +Z = backwards
+
+ wind_vec.setVec(-wind_vec.mV[1], wind_vec.mV[2], -wind_vec.mV[0]);
+
+ pitch = 1.0 + mapWindVecToPitch(wind_vec);
+ center_freq = 80.0 * pow(pitch,2.5*(mapWindVecToGain(wind_vec)+1.0));
+
+ mWindGen->mTargetFreq = (F32)center_freq;
+ mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain;
+ mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec);
+
+ alSourcei(mWindSource, AL_LOOPING, AL_FALSE);
+ alSource3f(mWindSource, AL_POSITION, 0.0, 0.0, 0.0);
+ alSource3f(mWindSource, AL_VELOCITY, 0.0, 0.0, 0.0);
+ alSourcef(mWindSource, AL_ROLLOFF_FACTOR, 0.0);
+ alSourcei(mWindSource, AL_SOURCE_RELATIVE, AL_TRUE);
+ }
+
+ // ok lets make a wind buffer now
+
+ ALint processed, queued, unprocessed;
+ alGetSourcei(mWindSource, AL_BUFFERS_PROCESSED, &processed);
+ alGetSourcei(mWindSource, AL_BUFFERS_QUEUED, &queued);
+ unprocessed = queued - processed;
+
+ // ensure that there are always at least 3x as many filled buffers
+ // queued as we managed to empty since last time.
+ mNumEmptyWindALBuffers = llmin(mNumEmptyWindALBuffers + processed * 3 - unprocessed, MAX_NUM_WIND_BUFFERS-unprocessed);
+ mNumEmptyWindALBuffers = llmax(mNumEmptyWindALBuffers, 0);
+
+ //LL_INFOS() << "mNumEmptyWindALBuffers: " << mNumEmptyWindALBuffers <<" (" << unprocessed << ":" << processed << ")" << LL_ENDL;
+
+ while(processed--) // unqueue old buffers
+ {
+ ALuint buffer;
+ ALenum error;
+ alGetError(); /* clear error */
+ alSourceUnqueueBuffers(mWindSource, 1, &buffer);
+ error = alGetError();
+ if(error != AL_NO_ERROR)
+ {
+ LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (unqueuing) buffers" << LL_ENDL;
+ }
+ else
+ {
+ alDeleteBuffers(1, &buffer);
+ }
+ }
+
+ unprocessed += mNumEmptyWindALBuffers;
+ while (mNumEmptyWindALBuffers > 0) // fill+queue new buffers
+ {
+ ALuint buffer;
+ alGetError(); /* clear error */
+ alGenBuffers(1,&buffer);
+ if((error=alGetError()) != AL_NO_ERROR)
+ {
+ LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() Error creating wind buffer: " << error << LL_ENDL;
+ break;
+ }
+
+ alBufferData(buffer,
+ AL_FORMAT_STEREO_FLOAT32,
+ mWindGen->windGenerate(mWindBuf,
+ mWindBufSamples),
+ mWindBufBytes,
+ mWindBufFreq);
+ error = alGetError();
+ if(error != AL_NO_ERROR)
+ {
+ LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (bufferdata) buffers" << LL_ENDL;
+ }
+
+ alSourceQueueBuffers(mWindSource, 1, &buffer);
+ error = alGetError();
+ if(error != AL_NO_ERROR)
+ {
+ LL_WARNS() << "LLAudioEngine_OpenAL::updateWind() error swapping (queuing) buffers" << LL_ENDL;
+ }
+
+ --mNumEmptyWindALBuffers;
+ }
+
+ ALint playing;
+ alGetSourcei(mWindSource, AL_SOURCE_STATE, &playing);
+ if(playing != AL_PLAYING)
+ {
+ alSourcePlay(mWindSource);
+
+ LL_DEBUGS() << "Wind had stopped - probably ran out of buffers - restarting: " << (unprocessed+mNumEmptyWindALBuffers) << " now queued." << LL_ENDL;
+ }
+}
+
diff --git a/indra/llaudio/llaudioengine_openal.h b/indra/llaudio/llaudioengine_openal.h index 9aadc84ddf..6875bcc68b 100644 --- a/indra/llaudio/llaudioengine_openal.h +++ b/indra/llaudio/llaudioengine_openal.h @@ -1,108 +1,108 @@ -/** - * @file audioengine_openal.cpp - * @brief implementation of audio engine using OpenAL - * support as a OpenAL 3D implementation - * - * - * $LicenseInfo:firstyear=2002&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$ - */ - - -#ifndef LL_AUDIOENGINE_OPENAL_H -#define LL_AUDIOENGINE_OPENAL_H - -#include "llaudioengine.h" -#include "lllistener_openal.h" -#include "llwindgen.h" - -class LLAudioEngine_OpenAL : public LLAudioEngine -{ - public: - LLAudioEngine_OpenAL(); - virtual ~LLAudioEngine_OpenAL(); - - virtual bool init(void *user_data, const std::string &app_title); - virtual std::string getDriverName(bool verbose); - virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const { return nullptr; } - virtual void allocateListener(); - - virtual void shutdown(); - - void setInternalGain(F32 gain); - - LLAudioBuffer* createBuffer(); - LLAudioChannel* createChannel(); - - /*virtual*/ bool initWind(); - /*virtual*/ void cleanupWind(); - /*virtual*/ void updateWind(LLVector3 direction, F32 camera_altitude); - - private: - typedef S16 WIND_SAMPLE_T; - LLWindGen<WIND_SAMPLE_T> *mWindGen; - S16 *mWindBuf; - U32 mWindBufFreq; - U32 mWindBufSamples; - U32 mWindBufBytes; - ALuint mWindSource; - int mNumEmptyWindALBuffers; - - static const int MAX_NUM_WIND_BUFFERS = 80; - static const float WIND_BUFFER_SIZE_SEC; // 1/20th sec -}; - -class LLAudioChannelOpenAL : public LLAudioChannel -{ - public: - LLAudioChannelOpenAL(); - virtual ~LLAudioChannelOpenAL(); - protected: - /*virtual*/ void play(); - /*virtual*/ void playSynced(LLAudioChannel *channelp); - /*virtual*/ void cleanup(); - /*virtual*/ bool isPlaying(); - - /*virtual*/ bool updateBuffer(); - /*virtual*/ void update3DPosition(); - /*virtual*/ void updateLoop(); - - ALuint mALSource; - ALint mLastSamplePos; -}; - -class LLAudioBufferOpenAL : public LLAudioBuffer{ - public: - LLAudioBufferOpenAL(); - virtual ~LLAudioBufferOpenAL(); - - bool loadWAV(const std::string& filename); - U32 getLength(); - - friend class LLAudioChannelOpenAL; - protected: - void cleanup(); - ALuint getBuffer() {return mALBuffer;} - - ALuint mALBuffer; -}; - -#endif +/**
+ * @file audioengine_openal.cpp
+ * @brief implementation of audio engine using OpenAL
+ * support as a OpenAL 3D implementation
+ *
+ *
+ * $LicenseInfo:firstyear=2002&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$
+ */
+
+
+#ifndef LL_AUDIOENGINE_OPENAL_H
+#define LL_AUDIOENGINE_OPENAL_H
+
+#include "llaudioengine.h"
+#include "lllistener_openal.h"
+#include "llwindgen.h"
+
+class LLAudioEngine_OpenAL : public LLAudioEngine
+{
+ public:
+ LLAudioEngine_OpenAL();
+ virtual ~LLAudioEngine_OpenAL();
+
+ virtual bool init(void *user_data, const std::string &app_title);
+ virtual std::string getDriverName(bool verbose);
+ virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const { return nullptr; }
+ virtual void allocateListener();
+
+ virtual void shutdown();
+
+ void setInternalGain(F32 gain);
+
+ LLAudioBuffer* createBuffer();
+ LLAudioChannel* createChannel();
+
+ /*virtual*/ bool initWind();
+ /*virtual*/ void cleanupWind();
+ /*virtual*/ void updateWind(LLVector3 direction, F32 camera_altitude);
+
+ private:
+ typedef F32 WIND_SAMPLE_T;
+ LLWindGen<WIND_SAMPLE_T> *mWindGen;
+ F32 *mWindBuf;
+ U32 mWindBufFreq;
+ U32 mWindBufSamples;
+ U32 mWindBufBytes;
+ ALuint mWindSource;
+ int mNumEmptyWindALBuffers;
+
+ static const int MAX_NUM_WIND_BUFFERS = 80;
+ static const float WIND_BUFFER_SIZE_SEC; // 1/20th sec
+};
+
+class LLAudioChannelOpenAL : public LLAudioChannel
+{
+ public:
+ LLAudioChannelOpenAL();
+ virtual ~LLAudioChannelOpenAL();
+ protected:
+ /*virtual*/ void play();
+ /*virtual*/ void playSynced(LLAudioChannel *channelp);
+ /*virtual*/ void cleanup();
+ /*virtual*/ bool isPlaying();
+
+ /*virtual*/ bool updateBuffer();
+ /*virtual*/ void update3DPosition();
+ /*virtual*/ void updateLoop();
+
+ ALuint mALSource;
+ ALint mLastSamplePos;
+};
+
+class LLAudioBufferOpenAL : public LLAudioBuffer{
+ public:
+ LLAudioBufferOpenAL();
+ virtual ~LLAudioBufferOpenAL();
+
+ bool loadWAV(const std::string& filename);
+ U32 getLength();
+
+ friend class LLAudioChannelOpenAL;
+ protected:
+ void cleanup();
+ ALuint getBuffer() {return mALBuffer;}
+
+ ALuint mALBuffer;
+};
+
+#endif
diff --git a/indra/llaudio/lllistener_fmodstudio.cpp b/indra/llaudio/lllistener_fmodstudio.cpp deleted file mode 100644 index 9dc55eec79..0000000000 --- a/indra/llaudio/lllistener_fmodstudio.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/** - * @file listener_fmodstudio.cpp - * @brief Implementation of LISTENER class abstracting the audio - * support as a FMODSTUDIO implementation - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, 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 "lllistener_fmodstudio.h" -#include "fmodstudio/fmod.hpp" - -//----------------------------------------------------------------------- -// constructor -//----------------------------------------------------------------------- -LLListener_FMODSTUDIO::LLListener_FMODSTUDIO(FMOD::System *system) -{ - mSystem = system; - init(); -} - -//----------------------------------------------------------------------- -LLListener_FMODSTUDIO::~LLListener_FMODSTUDIO() -{ -} - -//----------------------------------------------------------------------- -void LLListener_FMODSTUDIO::init(void) -{ - // do inherited - LLListener::init(); - mDopplerFactor = 1.0f; - mRolloffFactor = 1.0f; -} - -//----------------------------------------------------------------------- -void LLListener_FMODSTUDIO::translate(LLVector3 offset) -{ - LLListener::translate(offset); - - mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV); -} - -//----------------------------------------------------------------------- -void LLListener_FMODSTUDIO::setPosition(LLVector3 pos) -{ - LLListener::setPosition(pos); - - mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV); -} - -//----------------------------------------------------------------------- -void LLListener_FMODSTUDIO::setVelocity(LLVector3 vel) -{ - LLListener::setVelocity(vel); - - mSystem->set3DListenerAttributes(0, NULL, (FMOD_VECTOR*)mVelocity.mV, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV); -} - -//----------------------------------------------------------------------- -void LLListener_FMODSTUDIO::orient(LLVector3 up, LLVector3 at) -{ - LLListener::orient(up, at); - - // at = -at; by default Fmod studio is 'left-handed' but we are providing - // flag FMOD_INIT_3D_RIGHTHANDED so no correction are needed - - mSystem->set3DListenerAttributes(0, NULL, NULL, (FMOD_VECTOR*)at.mV, (FMOD_VECTOR*)up.mV); -} - -//----------------------------------------------------------------------- -void LLListener_FMODSTUDIO::commitDeferredChanges() -{ - if (!mSystem) - { - return; - } - - mSystem->update(); -} - - -void LLListener_FMODSTUDIO::setRolloffFactor(F32 factor) -{ - //An internal FMOD optimization skips 3D updates if there have not been changes to the 3D sound environment. - // (this was true for FMODex, looks to be still true for FMOD STUDIO, but needs a recheck) - //Sadly, a change in rolloff is not accounted for, thus we must touch the listener properties as well. - //In short: Changing the position ticks a dirtyflag inside fmod, which makes it not skip 3D processing next update call. - if (mRolloffFactor != factor) - { - LLVector3 pos = mPosition - LLVector3(0.f, 0.f, .1f); - mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)pos.mV, NULL, NULL, NULL); - mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, NULL, NULL); - } - mRolloffFactor = factor; - mSystem->set3DSettings(mDopplerFactor, 1.f, mRolloffFactor); -} - - -F32 LLListener_FMODSTUDIO::getRolloffFactor() -{ - return mRolloffFactor; -} - - -void LLListener_FMODSTUDIO::setDopplerFactor(F32 factor) -{ - mDopplerFactor = factor; - mSystem->set3DSettings(mDopplerFactor, 1.f, mRolloffFactor); -} - - -F32 LLListener_FMODSTUDIO::getDopplerFactor() -{ - return mDopplerFactor; -} diff --git a/indra/llaudio/lllistener_fmodstudio.h b/indra/llaudio/lllistener_fmodstudio.h deleted file mode 100644 index 5287cbedc6..0000000000 --- a/indra/llaudio/lllistener_fmodstudio.h +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @file listener_fmodstudio.h - * @brief Description of LISTENER class abstracting the audio support - * as an FMOD 3D implementation - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, 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$ - */ - -#ifndef LL_LISTENER_FMODSTUDIO_H -#define LL_LISTENER_FMODSTUDIO_H - -#include "lllistener.h" - -//Stubs -namespace FMOD -{ - class System; -} - -//Interfaces -class LLListener_FMODSTUDIO : public LLListener -{ -public: - LLListener_FMODSTUDIO(FMOD::System *system); - virtual ~LLListener_FMODSTUDIO(); - virtual void init(); - - virtual void translate(LLVector3 offset); - virtual void setPosition(LLVector3 pos); - virtual void setVelocity(LLVector3 vel); - virtual void orient(LLVector3 up, LLVector3 at); - virtual void commitDeferredChanges(); - - virtual void setDopplerFactor(F32 factor); - virtual F32 getDopplerFactor(); - virtual void setRolloffFactor(F32 factor); - virtual F32 getRolloffFactor(); -protected: - FMOD::System *mSystem; - F32 mDopplerFactor; - F32 mRolloffFactor; -}; - -#endif - - diff --git a/indra/llaudio/lllistener_openal.h b/indra/llaudio/lllistener_openal.h index a877270201..ca5eba8b4e 100644 --- a/indra/llaudio/lllistener_openal.h +++ b/indra/llaudio/lllistener_openal.h @@ -1,58 +1,59 @@ -/** - * @file listener_openal.h - * @brief Description of LISTENER class abstracting the audio support - * as an OpenAL implementation - * - * $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$ - */ - -#ifndef LL_LISTENER_OPENAL_H -#define LL_LISTENER_OPENAL_H - -#include "lllistener.h" - -#include "AL/al.h" -#include "AL/alut.h" - -class LLListener_OpenAL : public LLListener -{ - public: - LLListener_OpenAL(); - virtual ~LLListener_OpenAL(); - - virtual void translate(LLVector3 offset); - virtual void setPosition(LLVector3 pos); - virtual void setVelocity(LLVector3 vel); - virtual void orient(LLVector3 up, LLVector3 at); - virtual void commitDeferredChanges(); - - virtual void setDopplerFactor(F32 factor); - virtual F32 getDopplerFactor(); - virtual void setRolloffFactor(F32 factor); - virtual F32 getRolloffFactor(); - - protected: - F32 mRolloffFactor; -}; - -#endif - +/**
+ * @file listener_openal.h
+ * @brief Description of LISTENER class abstracting the audio support
+ * as an OpenAL implementation
+ *
+ * $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$
+ */
+
+#ifndef LL_LISTENER_OPENAL_H
+#define LL_LISTENER_OPENAL_H
+
+#include "lllistener.h"
+
+#include "AL/al.h"
+#include "AL/alut.h"
+#include "AL/alext.h"
+
+class LLListener_OpenAL : public LLListener
+{
+ public:
+ LLListener_OpenAL();
+ virtual ~LLListener_OpenAL();
+
+ virtual void translate(LLVector3 offset);
+ virtual void setPosition(LLVector3 pos);
+ virtual void setVelocity(LLVector3 vel);
+ virtual void orient(LLVector3 up, LLVector3 at);
+ virtual void commitDeferredChanges();
+
+ virtual void setDopplerFactor(F32 factor);
+ virtual F32 getDopplerFactor();
+ virtual void setRolloffFactor(F32 factor);
+ virtual F32 getRolloffFactor();
+
+ protected:
+ F32 mRolloffFactor;
+};
+
+#endif
+
diff --git a/indra/llaudio/llstreamingaudio_fmodstudio.cpp b/indra/llaudio/llstreamingaudio_fmodstudio.cpp deleted file mode 100644 index 22fc86c0cd..0000000000 --- a/indra/llaudio/llstreamingaudio_fmodstudio.cpp +++ /dev/null @@ -1,481 +0,0 @@ -/** - * @file streamingaudio_fmodstudio.cpp - * @brief LLStreamingAudio_FMODSTUDIO implementation - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, 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 "llmath.h" - -#include "fmodstudio/fmod.hpp" -#include "fmodstudio/fmod_errors.h" - -#include "llstreamingaudio_fmodstudio.h" - - -class LLAudioStreamManagerFMODSTUDIO -{ -public: - LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, const std::string& url); - FMOD::Channel* startStream(); - bool stopStream(); // Returns true if the stream was successfully stopped. - bool ready(); - - const std::string& getURL() { return mInternetStreamURL; } - - FMOD_OPENSTATE getOpenState(unsigned int* percentbuffered = NULL, bool* starving = NULL, bool* diskbusy = NULL); -protected: - FMOD::System* mSystem; - FMOD::Channel* mStreamChannel; - FMOD::Sound* mInternetStream; - bool mReady; - - std::string mInternetStreamURL; -}; - - - -//--------------------------------------------------------------------------- -// Internet Streaming -//--------------------------------------------------------------------------- -LLStreamingAudio_FMODSTUDIO::LLStreamingAudio_FMODSTUDIO(FMOD::System *system) : -mSystem(system), -mCurrentInternetStreamp(NULL), -mFMODInternetStreamChannelp(NULL), -mGain(1.0f), -mRetryCount(0) -{ - // Number of milliseconds of audio to buffer for the audio card. - // Must be larger than the usual Second Life frame stutter time. - const U32 buffer_seconds = 10; //sec - const U32 estimated_bitrate = 128; //kbit/sec - FMOD_RESULT result = mSystem->setStreamBufferSize(estimated_bitrate * buffer_seconds * 128/*bytes/kbit*/, FMOD_TIMEUNIT_RAWBYTES); - if (result != FMOD_OK) - { - LL_WARNS("FMOD") << "setStreamBufferSize error: " << FMOD_ErrorString(result) << LL_ENDL; - } - - // Here's where we set the size of the network buffer and some buffering - // parameters. In this case we want a network buffer of 16k, we want it - // to prebuffer 40% of that when we first connect, and we want it - // to rebuffer 80% of that whenever we encounter a buffer underrun. - - // Leave the net buffer properties at the default. - //FSOUND_Stream_Net_SetBufferProperties(20000, 40, 80); -} - - -LLStreamingAudio_FMODSTUDIO::~LLStreamingAudio_FMODSTUDIO() -{ - if (mCurrentInternetStreamp) - { - // Isn't supposed to hapen, stream should be clear by now, - // and if it does, we are likely going to crash. - LL_WARNS("FMOD") << "mCurrentInternetStreamp not null on shutdown!" << LL_ENDL; - stop(); - } - - // Kill dead internet streams, if possible - killDeadStreams(); - - if (!mDeadStreams.empty()) - { - // LLStreamingAudio_FMODSTUDIO was inited on startup - // and should be destroyed on shutdown, it should - // wait for streams to die to not cause crashes or - // leaks. - // Ideally we need to wait on some kind of callback - // to release() streams correctly, but 200 ms should - // be enough and we can't wait forever. - LL_INFOS("FMOD") << "Waiting for " << (S32)mDeadStreams.size() << " streams to stop" << LL_ENDL; - for (S32 i = 0; i < 20; i++) - { - const U32 ms_delay = 10; - ms_sleep(ms_delay); // rude, but not many options here - killDeadStreams(); - if (mDeadStreams.empty()) - { - LL_INFOS("FMOD") << "All streams stopped after " << (S32)((i + 1) * ms_delay) << "ms" << LL_ENDL; - break; - } - } - } - - if (!mDeadStreams.empty()) - { - LL_WARNS("FMOD") << "Failed to kill some audio streams" << LL_ENDL; - } -} - -void LLStreamingAudio_FMODSTUDIO::killDeadStreams() -{ - std::list<LLAudioStreamManagerFMODSTUDIO *>::iterator iter; - for (iter = mDeadStreams.begin(); iter != mDeadStreams.end();) - { - LLAudioStreamManagerFMODSTUDIO *streamp = *iter; - if (streamp->stopStream()) - { - LL_INFOS("FMOD") << "Closed dead stream" << LL_ENDL; - delete streamp; - iter = mDeadStreams.erase(iter); - } - else - { - iter++; - } - } -} - -void LLStreamingAudio_FMODSTUDIO::start(const std::string& url) -{ - //if (!mInited) - //{ - // LL_WARNS() << "startInternetStream before audio initialized" << LL_ENDL; - // return; - //} - - // "stop" stream but don't clear url, etc. in case url == mInternetStreamURL - stop(); - - if (!url.empty()) - { - LL_INFOS("FMOD") << "Starting internet stream: " << url << LL_ENDL; - mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem, url); - mURL = url; - } - else - { - LL_INFOS("FMOD") << "Set internet stream to null" << LL_ENDL; - mURL.clear(); - } - - mRetryCount = 0; -} - - -void LLStreamingAudio_FMODSTUDIO::update() -{ - // Kill dead internet streams, if possible - killDeadStreams(); - - // Don't do anything if there are no streams playing - if (!mCurrentInternetStreamp) - { - return; - } - - unsigned int progress; - bool starving; - bool diskbusy; - FMOD_OPENSTATE open_state = mCurrentInternetStreamp->getOpenState(&progress, &starving, &diskbusy); - - if (open_state == FMOD_OPENSTATE_READY) - { - // Stream is live - - // start the stream if it's ready - if (!mFMODInternetStreamChannelp && - (mFMODInternetStreamChannelp = mCurrentInternetStreamp->startStream())) - { - // Reset volume to previously set volume - setGain(getGain()); - mFMODInternetStreamChannelp->setPaused(false); - } - mRetryCount = 0; - } - else if (open_state == FMOD_OPENSTATE_ERROR) - { - LL_INFOS("FMOD") << "State: FMOD_OPENSTATE_ERROR" - << " Progress: " << U32(progress) - << " Starving: " << S32(starving) - << " Diskbusy: " << S32(diskbusy) << LL_ENDL; - if (mRetryCount < 2) - { - // Retry - std::string url = mURL; - stop(); // might drop mURL, drops mCurrentInternetStreamp - - mRetryCount++; - - if (!url.empty()) - { - LL_INFOS("FMOD") << "Restarting internet stream: " << url << ", attempt " << (mRetryCount + 1) << LL_ENDL; - mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem, url); - mURL = url; - } - } - else - { - stop(); - } - return; - } - - if (mFMODInternetStreamChannelp) - { - FMOD::Sound *sound = NULL; - - if (mFMODInternetStreamChannelp->getCurrentSound(&sound) == FMOD_OK && sound) - { - FMOD_TAG tag; - S32 tagcount, dirtytagcount; - - if (sound->getNumTags(&tagcount, &dirtytagcount) == FMOD_OK && dirtytagcount) - { - for (S32 i = 0; i < tagcount; ++i) - { - if (sound->getTag(NULL, i, &tag) != FMOD_OK) - continue; - - if (tag.type == FMOD_TAGTYPE_FMOD) - { - if (!strcmp(tag.name, "Sample Rate Change")) - { - LL_INFOS("FMOD") << "Stream forced changing sample rate to " << *((float *)tag.data) << LL_ENDL; - mFMODInternetStreamChannelp->setFrequency(*((float *)tag.data)); - } - continue; - } - } - } - - if (starving) - { - bool paused = false; - mFMODInternetStreamChannelp->getPaused(&paused); - if (!paused) - { - LL_INFOS("FMOD") << "Stream starvation detected! Pausing stream until buffer nearly full." << LL_ENDL; - LL_INFOS("FMOD") << " (diskbusy=" << diskbusy << ")" << LL_ENDL; - LL_INFOS("FMOD") << " (progress=" << progress << ")" << LL_ENDL; - mFMODInternetStreamChannelp->setPaused(true); - } - } - else if (progress > 80) - { - mFMODInternetStreamChannelp->setPaused(false); - } - } - } -} - -void LLStreamingAudio_FMODSTUDIO::stop() -{ - if (mFMODInternetStreamChannelp) - { - mFMODInternetStreamChannelp->setPaused(true); - mFMODInternetStreamChannelp->setPriority(0); - mFMODInternetStreamChannelp = NULL; - } - - if (mCurrentInternetStreamp) - { - LL_INFOS("FMOD") << "Stopping internet stream: " << mCurrentInternetStreamp->getURL() << LL_ENDL; - if (mCurrentInternetStreamp->stopStream()) - { - delete mCurrentInternetStreamp; - } - else - { - LL_WARNS("FMOD") << "Pushing stream to dead list: " << mCurrentInternetStreamp->getURL() << LL_ENDL; - mDeadStreams.push_back(mCurrentInternetStreamp); - } - mCurrentInternetStreamp = NULL; - //mURL.clear(); - } -} - -void LLStreamingAudio_FMODSTUDIO::pause(int pauseopt) -{ - if (pauseopt < 0) - { - pauseopt = mCurrentInternetStreamp ? 1 : 0; - } - - if (pauseopt) - { - if (mCurrentInternetStreamp) - { - LL_INFOS("FMOD") << "Pausing internet stream" << LL_ENDL; - stop(); - } - } - else - { - start(getURL()); - } -} - - -// A stream is "playing" if it has been requested to start. That -// doesn't necessarily mean audio is coming out of the speakers. -int LLStreamingAudio_FMODSTUDIO::isPlaying() -{ - if (mCurrentInternetStreamp) - { - return 1; // Active and playing - } - else if (!mURL.empty()) - { - return 2; // "Paused" - } - else - { - return 0; - } -} - - -F32 LLStreamingAudio_FMODSTUDIO::getGain() -{ - return mGain; -} - - -std::string LLStreamingAudio_FMODSTUDIO::getURL() -{ - return mURL; -} - - -void LLStreamingAudio_FMODSTUDIO::setGain(F32 vol) -{ - mGain = vol; - - if (mFMODInternetStreamChannelp) - { - vol = llclamp(vol * vol, 0.f, 1.f); //should vol be squared here? - - mFMODInternetStreamChannelp->setVolume(vol); - } -} - -/////////////////////////////////////////////////////// -// manager of possibly-multiple internet audio streams - -LLAudioStreamManagerFMODSTUDIO::LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, const std::string& url) : -mSystem(system), -mStreamChannel(NULL), -mInternetStream(NULL), -mReady(false) -{ - mInternetStreamURL = url; - - FMOD_RESULT result = mSystem->createStream(url.c_str(), FMOD_2D | FMOD_NONBLOCKING | FMOD_IGNORETAGS, 0, &mInternetStream); - - if (result != FMOD_OK) - { - LL_WARNS("FMOD") << "Couldn't open fmod stream, error " - << FMOD_ErrorString(result) - << LL_ENDL; - mReady = false; - return; - } - - mReady = true; -} - -FMOD::Channel *LLAudioStreamManagerFMODSTUDIO::startStream() -{ - // We need a live and opened stream before we try and play it. - if (!mInternetStream || getOpenState() != FMOD_OPENSTATE_READY) - { - LL_WARNS("FMOD") << "No internet stream to start playing!" << LL_ENDL; - return NULL; - } - - if (mStreamChannel) - return mStreamChannel; //Already have a channel for this stream. - - FMOD_RESULT result = mSystem->playSound(mInternetStream, NULL, true, &mStreamChannel); - if (result != FMOD_OK) - { - LL_WARNS("FMOD") << FMOD_ErrorString(result) << LL_ENDL; - } - return mStreamChannel; -} - -bool LLAudioStreamManagerFMODSTUDIO::stopStream() -{ - if (mInternetStream) - { - - - bool close = true; - switch (getOpenState()) - { - case FMOD_OPENSTATE_CONNECTING: - close = false; - break; - default: - close = true; - } - - if (close) - { - mInternetStream->release(); - mStreamChannel = NULL; - mInternetStream = NULL; - return true; - } - else - { - return false; - } - } - else - { - return true; - } -} - -FMOD_OPENSTATE LLAudioStreamManagerFMODSTUDIO::getOpenState(unsigned int* percentbuffered, bool* starving, bool* diskbusy) -{ - FMOD_OPENSTATE state; - FMOD_RESULT result = mInternetStream->getOpenState(&state, percentbuffered, starving, diskbusy); - if (result != FMOD_OK) - { - LL_WARNS("FMOD") << FMOD_ErrorString(result) << LL_ENDL; - } - return state; -} - -void LLStreamingAudio_FMODSTUDIO::setBufferSizes(U32 streambuffertime, U32 decodebuffertime) -{ - FMOD_RESULT result = mSystem->setStreamBufferSize(streambuffertime / 1000 * 128 * 128, FMOD_TIMEUNIT_RAWBYTES); - if (result != FMOD_OK) - { - LL_WARNS("FMOD") << "setStreamBufferSize error: " << FMOD_ErrorString(result) << LL_ENDL; - return; - } - FMOD_ADVANCEDSETTINGS settings; - memset(&settings, 0, sizeof(settings)); - settings.cbSize = sizeof(settings); - settings.defaultDecodeBufferSize = decodebuffertime;//ms - result = mSystem->setAdvancedSettings(&settings); - if (result != FMOD_OK) - { - LL_WARNS("FMOD") << "setAdvancedSettings error: " << FMOD_ErrorString(result) << LL_ENDL; - } -} diff --git a/indra/llaudio/llstreamingaudio_fmodstudio.h b/indra/llaudio/llstreamingaudio_fmodstudio.h deleted file mode 100644 index 2127841809..0000000000 --- a/indra/llaudio/llstreamingaudio_fmodstudio.h +++ /dev/null @@ -1,76 +0,0 @@ -/** - * @file streamingaudio_fmodstudio.h - * @brief Definition of LLStreamingAudio_FMODSTUDIO implementation - * - * $LicenseInfo:firstyear=2020&license=viewerlgpl$ - * Second Life Viewer Source Code - * Copyright (C) 2020, 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$ - */ - -#ifndef LL_STREAMINGAUDIO_FMODSTUDIO_H -#define LL_STREAMINGAUDIO_FMODSTUDIO_H - -#include "stdtypes.h" // from llcommon - -#include "llstreamingaudio.h" -#include "lltimer.h" - -//Stubs -class LLAudioStreamManagerFMODSTUDIO; -namespace FMOD -{ - class System; - class Channel; -} - -//Interfaces -class LLStreamingAudio_FMODSTUDIO : public LLStreamingAudioInterface -{ -public: - LLStreamingAudio_FMODSTUDIO(FMOD::System *system); - /*virtual*/ ~LLStreamingAudio_FMODSTUDIO(); - - /*virtual*/ void start(const std::string& url); - /*virtual*/ void stop(); - /*virtual*/ void pause(S32 pause); - /*virtual*/ void update(); - /*virtual*/ S32 isPlaying(); - /*virtual*/ void setGain(F32 vol); - /*virtual*/ F32 getGain(); - /*virtual*/ std::string getURL(); - - /*virtual*/ bool supportsAdjustableBufferSizes(){return true;} - /*virtual*/ void setBufferSizes(U32 streambuffertime, U32 decodebuffertime); -private: - void killDeadStreams(); - - FMOD::System *mSystem; - - LLAudioStreamManagerFMODSTUDIO *mCurrentInternetStreamp; - FMOD::Channel *mFMODInternetStreamChannelp; - std::list<LLAudioStreamManagerFMODSTUDIO *> mDeadStreams; - - std::string mURL; - F32 mGain; - S32 mRetryCount; -}; - - -#endif // LL_STREAMINGAUDIO_FMODSTUDIO_H diff --git a/indra/llaudio/llvorbisencode.cpp b/indra/llaudio/llvorbisencode.cpp index 573c947764..908175038e 100644 --- a/indra/llaudio/llvorbisencode.cpp +++ b/indra/llaudio/llvorbisencode.cpp @@ -1,506 +1,506 @@ -/** - * @file vorbisencode.cpp - * @brief Vorbis encoding routine routine for Indra. - * - * $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 "vorbis/vorbisenc.h" - -#include "llvorbisencode.h" -#include "llerror.h" -#include "llrand.h" -#include "llmath.h" -#include "llapr.h" - -//#if LL_DARWIN -// MBW -- XXX -- Getting rid of SecondLifeVorbis for now -#if 0 -#include "VorbisFramework.h" - -#define vorbis_analysis mac_vorbis_analysis -#define vorbis_analysis_headerout mac_vorbis_analysis_headerout -#define vorbis_analysis_init mac_vorbis_analysis_init -#define vorbis_encode_ctl mac_vorbis_encode_ctl -#define vorbis_encode_setup_init mac_vorbis_encode_setup_init -#define vorbis_encode_setup_managed mac_vorbis_encode_setup_managed - -#define vorbis_info_init mac_vorbis_info_init -#define vorbis_info_clear mac_vorbis_info_clear -#define vorbis_comment_init mac_vorbis_comment_init -#define vorbis_comment_clear mac_vorbis_comment_clear -#define vorbis_block_init mac_vorbis_block_init -#define vorbis_block_clear mac_vorbis_block_clear -#define vorbis_dsp_clear mac_vorbis_dsp_clear -#define vorbis_analysis_buffer mac_vorbis_analysis_buffer -#define vorbis_analysis_wrote mac_vorbis_analysis_wrote -#define vorbis_analysis_blockout mac_vorbis_analysis_blockout - -#define ogg_stream_packetin mac_ogg_stream_packetin -#define ogg_stream_init mac_ogg_stream_init -#define ogg_stream_flush mac_ogg_stream_flush -#define ogg_stream_pageout mac_ogg_stream_pageout -#define ogg_page_eos mac_ogg_page_eos -#define ogg_stream_clear mac_ogg_stream_clear - -#endif - -S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg) -{ - U16 num_channels = 0; - U32 sample_rate = 0; - U32 bits_per_sample = 0; - U32 physical_file_size = 0; - U32 chunk_length = 0; - U32 raw_data_length = 0; - U32 bytes_per_sec = 0; - BOOL uncompressed_pcm = FALSE; - - unsigned char wav_header[44]; /*Flawfinder: ignore*/ - - error_msg.clear(); - - //******************************** - LLAPRFile infile ; - infile.open(in_fname,LL_APR_RB); - //******************************** - if (!infile.getFileHandle()) - { - error_msg = "CannotUploadSoundFile"; - return(LLVORBISENC_SOURCE_OPEN_ERR); - } - - infile.read(wav_header, 44); - physical_file_size = infile.seek(APR_END,0); - - if (strncmp((char *)&(wav_header[0]),"RIFF",4)) - { - error_msg = "SoundFileNotRIFF"; - return(LLVORBISENC_WAV_FORMAT_ERR); - } - - if (strncmp((char *)&(wav_header[8]),"WAVE",4)) - { - error_msg = "SoundFileNotRIFF"; - return(LLVORBISENC_WAV_FORMAT_ERR); - } - - // parse the chunks - - U32 file_pos = 12; // start at the first chunk (usually fmt but not always) - - while ((file_pos + 8)< physical_file_size) - { - infile.seek(APR_SET,file_pos); - infile.read(wav_header, 44); - - chunk_length = ((U32) wav_header[7] << 24) - + ((U32) wav_header[6] << 16) - + ((U32) wav_header[5] << 8) - + wav_header[4]; - - if (chunk_length > physical_file_size - file_pos - 4) - { - infile.close(); - error_msg = "SoundFileInvalidChunkSize"; - return(LLVORBISENC_CHUNK_SIZE_ERR); - } - -// LL_INFOS() << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << LL_ENDL; - - if (!(strncmp((char *)&(wav_header[0]),"fmt ",4))) - { - if ((wav_header[8] == 0x01) && (wav_header[9] == 0x00)) - { - uncompressed_pcm = TRUE; - } - num_channels = ((U16) wav_header[11] << 8) + wav_header[10]; - sample_rate = ((U32) wav_header[15] << 24) - + ((U32) wav_header[14] << 16) - + ((U32) wav_header[13] << 8) - + wav_header[12]; - bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22]; - bytes_per_sec = ((U32) wav_header[19] << 24) - + ((U32) wav_header[18] << 16) - + ((U32) wav_header[17] << 8) - + wav_header[16]; - } - else if (!(strncmp((char *)&(wav_header[0]),"data",4))) - { - raw_data_length = chunk_length; - } - file_pos += (chunk_length + 8); - chunk_length = 0; - } - //**************** - infile.close(); - //**************** - - if (!uncompressed_pcm) - { - error_msg = "SoundFileNotPCM"; - return(LLVORBISENC_PCM_FORMAT_ERR); - } - - if ((num_channels < 1) || (num_channels > LLVORBIS_CLIP_MAX_CHANNELS)) - { - error_msg = "SoundFileInvalidChannelCount"; - return(LLVORBISENC_MULTICHANNEL_ERR); - } - - if (sample_rate != LLVORBIS_CLIP_SAMPLE_RATE) - { - error_msg = "SoundFileInvalidSampleRate"; - return(LLVORBISENC_UNSUPPORTED_SAMPLE_RATE); - } - - if ((bits_per_sample != 16) && (bits_per_sample != 8)) - { - error_msg = "SoundFileInvalidWordSize"; - return(LLVORBISENC_UNSUPPORTED_WORD_SIZE); - } - - if (!raw_data_length) - { - error_msg = "SoundFileInvalidHeader"; - return(LLVORBISENC_CLIP_TOO_LONG); - } - - F32 clip_length = (F32)raw_data_length/(F32)bytes_per_sec; - - if (clip_length > LLVORBIS_CLIP_MAX_TIME) - { - error_msg = "SoundFileInvalidTooLong"; - return(LLVORBISENC_CLIP_TOO_LONG); - } - - return(LLVORBISENC_NOERR); -} - -S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname) -{ -#define READ_BUFFER 1024 - unsigned char readbuffer[READ_BUFFER*4+44]; /* out of the data segment, not the stack */ /*Flawfinder: ignore*/ - - ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */ - ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */ - ogg_packet op; /* one raw packet of data for decode */ - - vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */ - vorbis_comment vc; /* struct that stores all the user comments */ - - vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */ - vorbis_block vb; /* local working space for packet->PCM decode */ - - int eos=0; - int result; - - U16 num_channels = 0; - U32 sample_rate = 0; - U32 bits_per_sample = 0; - - S32 format_error = 0; - std::string error_msg; - if ((format_error = check_for_invalid_wav_formats(in_fname, error_msg))) - { - LL_WARNS() << error_msg << ": " << in_fname << LL_ENDL; - return(format_error); - } - -#if 1 - unsigned char wav_header[44]; /*Flawfinder: ignore*/ - - S32 data_left = 0; - - LLAPRFile infile ; - infile.open(in_fname,LL_APR_RB); - if (!infile.getFileHandle()) - { - LL_WARNS() << "Couldn't open temporary ogg file for writing: " << in_fname - << LL_ENDL; - return(LLVORBISENC_SOURCE_OPEN_ERR); - } - - LLAPRFile outfile ; - outfile.open(out_fname,LL_APR_WPB); - if (!outfile.getFileHandle()) - { - LL_WARNS() << "Couldn't open upload sound file for reading: " << in_fname - << LL_ENDL; - return(LLVORBISENC_DEST_OPEN_ERR); - } - - // parse the chunks - U32 chunk_length = 0; - U32 file_pos = 12; // start at the first chunk (usually fmt but not always) - - while (infile.eof() != APR_EOF) - { - infile.seek(APR_SET,file_pos); - infile.read(wav_header, 44); - - chunk_length = ((U32) wav_header[7] << 24) - + ((U32) wav_header[6] << 16) - + ((U32) wav_header[5] << 8) - + wav_header[4]; - -// LL_INFOS() << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << LL_ENDL; - - if (!(strncmp((char *)&(wav_header[0]),"fmt ",4))) - { - num_channels = ((U16) wav_header[11] << 8) + wav_header[10]; - sample_rate = ((U32) wav_header[15] << 24) - + ((U32) wav_header[14] << 16) - + ((U32) wav_header[13] << 8) - + wav_header[12]; - bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22]; - } - else if (!(strncmp((char *)&(wav_header[0]),"data",4))) - { - infile.seek(APR_SET,file_pos+8); - // leave the file pointer at the beginning of the data chunk data - data_left = chunk_length; - break; - } - file_pos += (chunk_length + 8); - chunk_length = 0; - } - - - /********** Encode setup ************/ - - /* choose an encoding mode */ - /* (mode 0: 44kHz stereo uncoupled, roughly 128kbps VBR) */ - vorbis_info_init(&vi); - - // always encode to mono - - // SL-52913 & SL-53779 determined this quality level to be our 'good - // enough' general-purpose quality level with a nice low bitrate. - // Equivalent to oggenc -q0.5 - F32 quality = 0.05f; -// quality = (bitrate==128000 ? 0.4f : 0.1); - -// if (vorbis_encode_init(&vi, /* num_channels */ 1 ,sample_rate, -1, bitrate, -1)) - if (vorbis_encode_init_vbr(&vi, /* num_channels */ 1 ,sample_rate, quality)) -// if (vorbis_encode_setup_managed(&vi,1,sample_rate,-1,bitrate,-1) || -// vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE_AVG,NULL) || -// vorbis_encode_setup_init(&vi)) - { - LL_WARNS() << "unable to initialize vorbis codec at quality " << quality << LL_ENDL; - // LL_WARNS() << "unable to initialize vorbis codec at bitrate " << bitrate << LL_ENDL; - return(LLVORBISENC_DEST_OPEN_ERR); - } - - /* add a comment */ - vorbis_comment_init(&vc); -// vorbis_comment_add(&vc,"Linden"); - - /* set up the analysis state and auxiliary encoding storage */ - vorbis_analysis_init(&vd,&vi); - vorbis_block_init(&vd,&vb); - - /* set up our packet->stream encoder */ - /* pick a random serial number; that way we can more likely build - chained streams just by concatenation */ - ogg_stream_init(&os, ll_rand()); - - /* Vorbis streams begin with three headers; the initial header (with - most of the codec setup parameters) which is mandated by the Ogg - bitstream spec. The second header holds any comment fields. The - third header holds the bitstream codebook. We merely need to - make the headers, then pass them to libvorbis one at a time; - libvorbis handles the additional Ogg bitstream constraints */ - - { - ogg_packet header; - ogg_packet header_comm; - ogg_packet header_code; - - vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code); - ogg_stream_packetin(&os,&header); /* automatically placed in its own - page */ - ogg_stream_packetin(&os,&header_comm); - ogg_stream_packetin(&os,&header_code); - - /* We don't have to write out here, but doing so makes streaming - * much easier, so we do, flushing ALL pages. This ensures the actual - * audio data will start on a new page - */ - while(!eos){ - int result=ogg_stream_flush(&os,&og); - if(result==0)break; - outfile.write(og.header, og.header_len); - outfile.write(og.body, og.body_len); - } - - } - - - while(!eos) - { - long bytes_per_sample = bits_per_sample/8; - - long bytes=(long)infile.read(readbuffer,llclamp((S32)(READ_BUFFER*num_channels*bytes_per_sample),0,data_left)); /* stereo hardwired here */ - - if (bytes==0) - { - /* end of file. this can be done implicitly in the mainline, - but it's easier to see here in non-clever fashion. - Tell the library we're at end of stream so that it can handle - the last frame and mark end of stream in the output properly */ - - vorbis_analysis_wrote(&vd,0); -// eos = 1; - - } - else - { - long i; - long samples; - int temp; - - data_left -= bytes; - /* data to encode */ - - /* expose the buffer to submit data */ - float **buffer=vorbis_analysis_buffer(&vd,READ_BUFFER); - - i = 0; - samples = bytes / (num_channels * bytes_per_sample); - - if (num_channels == 2) - { - if (bytes_per_sample == 2) - { - /* uninterleave samples */ - for(i=0; i<samples ;i++) - { - temp = ((signed char *)readbuffer)[i*4+1]; /*Flawfinder: ignore*/ - temp += ((signed char *)readbuffer)[i*4+3]; /*Flawfinder: ignore*/ - temp <<= 8; - temp += readbuffer[i*4]; - temp += readbuffer[i*4+2]; - - buffer[0][i] = ((float)temp) / 65536.f; - } - } - else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard") - { - /* uninterleave samples */ - for(i=0; i<samples ;i++) - { - temp = readbuffer[i*2+0]; - temp += readbuffer[i*2+1]; - temp -= 256; - buffer[0][i] = ((float)temp) / 256.f; - } - } - } - else if (num_channels == 1) - { - if (bytes_per_sample == 2) - { - for(i=0; i < samples ;i++) - { - temp = ((signed char*)readbuffer)[i*2+1]; - temp <<= 8; - temp += readbuffer[i*2]; - buffer[0][i] = ((float)temp) / 32768.f; - } - } - else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard") - { - for(i=0; i < samples ;i++) - { - temp = readbuffer[i]; - temp -= 128; - buffer[0][i] = ((float)temp) / 128.f; - } - } - } - - /* tell the library how much we actually submitted */ - vorbis_analysis_wrote(&vd,i); - } - - /* vorbis does some data preanalysis, then divvies up blocks for - more involved (potentially parallel) processing. Get a single - block for encoding now */ - while(vorbis_analysis_blockout(&vd,&vb)==1) - { - - /* analysis */ - /* Do the main analysis, creating a packet */ - vorbis_analysis(&vb, NULL); - vorbis_bitrate_addblock(&vb); - - while(vorbis_bitrate_flushpacket(&vd, &op)) - { - - /* weld the packet into the bitstream */ - ogg_stream_packetin(&os,&op); - - /* write out pages (if any) */ - while(!eos) - { - result = ogg_stream_pageout(&os,&og); - - if(result==0) - break; - - outfile.write(og.header, og.header_len); - outfile.write(og.body, og.body_len); - - /* this could be set above, but for illustrative purposes, I do - it here (to show that vorbis does know where the stream ends) */ - - if(ogg_page_eos(&og)) - eos=1; - - } - } - } - } - - - - /* clean up and exit. vorbis_info_clear() must be called last */ - - ogg_stream_clear(&os); - vorbis_block_clear(&vb); - vorbis_dsp_clear(&vd); - vorbis_comment_clear(&vc); - vorbis_info_clear(&vi); - - /* ogg_page and ogg_packet structs always point to storage in - libvorbis. They're never freed or manipulated directly */ - -// fprintf(stderr,"Vorbis encoding: Done.\n"); - LL_INFOS() << "Vorbis encoding: Done." << LL_ENDL; - -#endif - return(LLVORBISENC_NOERR); - -} +/**
+ * @file vorbisencode.cpp
+ * @brief Vorbis encoding routine routine for Indra.
+ *
+ * $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 "vorbis/vorbisenc.h"
+
+#include "llvorbisencode.h"
+#include "llerror.h"
+#include "llrand.h"
+#include "llmath.h"
+#include "llapr.h"
+
+//#if LL_DARWIN
+// MBW -- XXX -- Getting rid of SecondLifeVorbis for now
+#if 0
+#include "VorbisFramework.h"
+
+#define vorbis_analysis mac_vorbis_analysis
+#define vorbis_analysis_headerout mac_vorbis_analysis_headerout
+#define vorbis_analysis_init mac_vorbis_analysis_init
+#define vorbis_encode_ctl mac_vorbis_encode_ctl
+#define vorbis_encode_setup_init mac_vorbis_encode_setup_init
+#define vorbis_encode_setup_managed mac_vorbis_encode_setup_managed
+
+#define vorbis_info_init mac_vorbis_info_init
+#define vorbis_info_clear mac_vorbis_info_clear
+#define vorbis_comment_init mac_vorbis_comment_init
+#define vorbis_comment_clear mac_vorbis_comment_clear
+#define vorbis_block_init mac_vorbis_block_init
+#define vorbis_block_clear mac_vorbis_block_clear
+#define vorbis_dsp_clear mac_vorbis_dsp_clear
+#define vorbis_analysis_buffer mac_vorbis_analysis_buffer
+#define vorbis_analysis_wrote mac_vorbis_analysis_wrote
+#define vorbis_analysis_blockout mac_vorbis_analysis_blockout
+
+#define ogg_stream_packetin mac_ogg_stream_packetin
+#define ogg_stream_init mac_ogg_stream_init
+#define ogg_stream_flush mac_ogg_stream_flush
+#define ogg_stream_pageout mac_ogg_stream_pageout
+#define ogg_page_eos mac_ogg_page_eos
+#define ogg_stream_clear mac_ogg_stream_clear
+
+#endif
+
+S32 check_for_invalid_wav_formats(const std::string& in_fname, std::string& error_msg)
+{
+ U16 num_channels = 0;
+ U32 sample_rate = 0;
+ U32 bits_per_sample = 0;
+ U32 physical_file_size = 0;
+ U32 chunk_length = 0;
+ U32 raw_data_length = 0;
+ U32 bytes_per_sec = 0;
+ bool uncompressed_pcm = false;
+
+ unsigned char wav_header[44]; /*Flawfinder: ignore*/
+
+ error_msg.clear();
+
+ //********************************
+ LLAPRFile infile ;
+ infile.open(in_fname,LL_APR_RB);
+ //********************************
+ if (!infile.getFileHandle())
+ {
+ error_msg = "CannotUploadSoundFile";
+ return(LLVORBISENC_SOURCE_OPEN_ERR);
+ }
+
+ infile.read(wav_header, 44);
+ physical_file_size = infile.seek(APR_END,0);
+
+ if (strncmp((char *)&(wav_header[0]),"RIFF",4))
+ {
+ error_msg = "SoundFileNotRIFF";
+ return(LLVORBISENC_WAV_FORMAT_ERR);
+ }
+
+ if (strncmp((char *)&(wav_header[8]),"WAVE",4))
+ {
+ error_msg = "SoundFileNotRIFF";
+ return(LLVORBISENC_WAV_FORMAT_ERR);
+ }
+
+ // parse the chunks
+
+ U32 file_pos = 12; // start at the first chunk (usually fmt but not always)
+
+ while ((file_pos + 8)< physical_file_size)
+ {
+ infile.seek(APR_SET,file_pos);
+ infile.read(wav_header, 44);
+
+ chunk_length = ((U32) wav_header[7] << 24)
+ + ((U32) wav_header[6] << 16)
+ + ((U32) wav_header[5] << 8)
+ + wav_header[4];
+
+ if (chunk_length > physical_file_size - file_pos - 4)
+ {
+ infile.close();
+ error_msg = "SoundFileInvalidChunkSize";
+ return(LLVORBISENC_CHUNK_SIZE_ERR);
+ }
+
+// LL_INFOS() << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << LL_ENDL;
+
+ if (!(strncmp((char *)&(wav_header[0]),"fmt ",4)))
+ {
+ if ((wav_header[8] == 0x01) && (wav_header[9] == 0x00))
+ {
+ uncompressed_pcm = true;
+ }
+ num_channels = ((U16) wav_header[11] << 8) + wav_header[10];
+ sample_rate = ((U32) wav_header[15] << 24)
+ + ((U32) wav_header[14] << 16)
+ + ((U32) wav_header[13] << 8)
+ + wav_header[12];
+ bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22];
+ bytes_per_sec = ((U32) wav_header[19] << 24)
+ + ((U32) wav_header[18] << 16)
+ + ((U32) wav_header[17] << 8)
+ + wav_header[16];
+ }
+ else if (!(strncmp((char *)&(wav_header[0]),"data",4)))
+ {
+ raw_data_length = chunk_length;
+ }
+ file_pos += (chunk_length + 8);
+ chunk_length = 0;
+ }
+ //****************
+ infile.close();
+ //****************
+
+ if (!uncompressed_pcm)
+ {
+ error_msg = "SoundFileNotPCM";
+ return(LLVORBISENC_PCM_FORMAT_ERR);
+ }
+
+ if ((num_channels < 1) || (num_channels > LLVORBIS_CLIP_MAX_CHANNELS))
+ {
+ error_msg = "SoundFileInvalidChannelCount";
+ return(LLVORBISENC_MULTICHANNEL_ERR);
+ }
+
+ if (sample_rate != LLVORBIS_CLIP_SAMPLE_RATE)
+ {
+ error_msg = "SoundFileInvalidSampleRate";
+ return(LLVORBISENC_UNSUPPORTED_SAMPLE_RATE);
+ }
+
+ if ((bits_per_sample != 16) && (bits_per_sample != 8))
+ {
+ error_msg = "SoundFileInvalidWordSize";
+ return(LLVORBISENC_UNSUPPORTED_WORD_SIZE);
+ }
+
+ if (!raw_data_length)
+ {
+ error_msg = "SoundFileInvalidHeader";
+ return(LLVORBISENC_CLIP_TOO_LONG);
+ }
+
+ F32 clip_length = (F32)raw_data_length/(F32)bytes_per_sec;
+
+ if (clip_length > LLVORBIS_CLIP_MAX_TIME)
+ {
+ error_msg = "SoundFileInvalidTooLong";
+ return(LLVORBISENC_CLIP_TOO_LONG);
+ }
+
+ return(LLVORBISENC_NOERR);
+}
+
+S32 encode_vorbis_file(const std::string& in_fname, const std::string& out_fname)
+{
+#define READ_BUFFER 1024
+ unsigned char readbuffer[READ_BUFFER*4+44]; /* out of the data segment, not the stack */ /*Flawfinder: ignore*/
+
+ ogg_stream_state os; /* take physical pages, weld into a logical stream of packets */
+ ogg_page og; /* one Ogg bitstream page. Vorbis packets are inside */
+ ogg_packet op; /* one raw packet of data for decode */
+
+ vorbis_info vi; /* struct that stores all the static vorbis bitstream settings */
+ vorbis_comment vc; /* struct that stores all the user comments */
+
+ vorbis_dsp_state vd; /* central working state for the packet->PCM decoder */
+ vorbis_block vb; /* local working space for packet->PCM decode */
+
+ int eos=0;
+ int result;
+
+ U16 num_channels = 0;
+ U32 sample_rate = 0;
+ U32 bits_per_sample = 0;
+
+ S32 format_error = 0;
+ std::string error_msg;
+ if ((format_error = check_for_invalid_wav_formats(in_fname, error_msg)))
+ {
+ LL_WARNS() << error_msg << ": " << in_fname << LL_ENDL;
+ return(format_error);
+ }
+
+#if 1
+ unsigned char wav_header[44]; /*Flawfinder: ignore*/
+
+ S32 data_left = 0;
+
+ LLAPRFile infile ;
+ infile.open(in_fname,LL_APR_RB);
+ if (!infile.getFileHandle())
+ {
+ LL_WARNS() << "Couldn't open temporary ogg file for writing: " << in_fname
+ << LL_ENDL;
+ return(LLVORBISENC_SOURCE_OPEN_ERR);
+ }
+
+ LLAPRFile outfile ;
+ outfile.open(out_fname,LL_APR_WPB);
+ if (!outfile.getFileHandle())
+ {
+ LL_WARNS() << "Couldn't open upload sound file for reading: " << in_fname
+ << LL_ENDL;
+ return(LLVORBISENC_DEST_OPEN_ERR);
+ }
+
+ // parse the chunks
+ U32 chunk_length = 0;
+ U32 file_pos = 12; // start at the first chunk (usually fmt but not always)
+
+ while (infile.eof() != APR_EOF)
+ {
+ infile.seek(APR_SET,file_pos);
+ infile.read(wav_header, 44);
+
+ chunk_length = ((U32) wav_header[7] << 24)
+ + ((U32) wav_header[6] << 16)
+ + ((U32) wav_header[5] << 8)
+ + wav_header[4];
+
+// LL_INFOS() << "chunk found: '" << wav_header[0] << wav_header[1] << wav_header[2] << wav_header[3] << "'" << LL_ENDL;
+
+ if (!(strncmp((char *)&(wav_header[0]),"fmt ",4)))
+ {
+ num_channels = ((U16) wav_header[11] << 8) + wav_header[10];
+ sample_rate = ((U32) wav_header[15] << 24)
+ + ((U32) wav_header[14] << 16)
+ + ((U32) wav_header[13] << 8)
+ + wav_header[12];
+ bits_per_sample = ((U16) wav_header[23] << 8) + wav_header[22];
+ }
+ else if (!(strncmp((char *)&(wav_header[0]),"data",4)))
+ {
+ infile.seek(APR_SET,file_pos+8);
+ // leave the file pointer at the beginning of the data chunk data
+ data_left = chunk_length;
+ break;
+ }
+ file_pos += (chunk_length + 8);
+ chunk_length = 0;
+ }
+
+
+ /********** Encode setup ************/
+
+ /* choose an encoding mode */
+ /* (mode 0: 44kHz stereo uncoupled, roughly 128kbps VBR) */
+ vorbis_info_init(&vi);
+
+ // always encode to mono
+
+ // SL-52913 & SL-53779 determined this quality level to be our 'good
+ // enough' general-purpose quality level with a nice low bitrate.
+ // Equivalent to oggenc -q0.5
+ F32 quality = 0.05f;
+// quality = (bitrate==128000 ? 0.4f : 0.1);
+
+// if (vorbis_encode_init(&vi, /* num_channels */ 1 ,sample_rate, -1, bitrate, -1))
+ if (vorbis_encode_init_vbr(&vi, /* num_channels */ 1 ,sample_rate, quality))
+// if (vorbis_encode_setup_managed(&vi,1,sample_rate,-1,bitrate,-1) ||
+// vorbis_encode_ctl(&vi,OV_ECTL_RATEMANAGE_AVG,NULL) ||
+// vorbis_encode_setup_init(&vi))
+ {
+ LL_WARNS() << "unable to initialize vorbis codec at quality " << quality << LL_ENDL;
+ // LL_WARNS() << "unable to initialize vorbis codec at bitrate " << bitrate << LL_ENDL;
+ return(LLVORBISENC_DEST_OPEN_ERR);
+ }
+
+ /* add a comment */
+ vorbis_comment_init(&vc);
+// vorbis_comment_add(&vc,"Linden");
+
+ /* set up the analysis state and auxiliary encoding storage */
+ vorbis_analysis_init(&vd,&vi);
+ vorbis_block_init(&vd,&vb);
+
+ /* set up our packet->stream encoder */
+ /* pick a random serial number; that way we can more likely build
+ chained streams just by concatenation */
+ ogg_stream_init(&os, ll_rand());
+
+ /* Vorbis streams begin with three headers; the initial header (with
+ most of the codec setup parameters) which is mandated by the Ogg
+ bitstream spec. The second header holds any comment fields. The
+ third header holds the bitstream codebook. We merely need to
+ make the headers, then pass them to libvorbis one at a time;
+ libvorbis handles the additional Ogg bitstream constraints */
+
+ {
+ ogg_packet header;
+ ogg_packet header_comm;
+ ogg_packet header_code;
+
+ vorbis_analysis_headerout(&vd,&vc,&header,&header_comm,&header_code);
+ ogg_stream_packetin(&os,&header); /* automatically placed in its own
+ page */
+ ogg_stream_packetin(&os,&header_comm);
+ ogg_stream_packetin(&os,&header_code);
+
+ /* We don't have to write out here, but doing so makes streaming
+ * much easier, so we do, flushing ALL pages. This ensures the actual
+ * audio data will start on a new page
+ */
+ while(!eos){
+ int result=ogg_stream_flush(&os,&og);
+ if(result==0)break;
+ outfile.write(og.header, og.header_len);
+ outfile.write(og.body, og.body_len);
+ }
+
+ }
+
+
+ while(!eos)
+ {
+ long bytes_per_sample = bits_per_sample/8;
+
+ long bytes=(long)infile.read(readbuffer,llclamp((S32)(READ_BUFFER*num_channels*bytes_per_sample),0,data_left)); /* stereo hardwired here */
+
+ if (bytes==0)
+ {
+ /* end of file. this can be done implicitly in the mainline,
+ but it's easier to see here in non-clever fashion.
+ Tell the library we're at end of stream so that it can handle
+ the last frame and mark end of stream in the output properly */
+
+ vorbis_analysis_wrote(&vd,0);
+// eos = 1;
+
+ }
+ else
+ {
+ long i;
+ long samples;
+ int temp;
+
+ data_left -= bytes;
+ /* data to encode */
+
+ /* expose the buffer to submit data */
+ float **buffer=vorbis_analysis_buffer(&vd,READ_BUFFER);
+
+ i = 0;
+ samples = bytes / (num_channels * bytes_per_sample);
+
+ if (num_channels == 2)
+ {
+ if (bytes_per_sample == 2)
+ {
+ /* uninterleave samples */
+ for(i=0; i<samples ;i++)
+ {
+ temp = ((signed char *)readbuffer)[i*4+1]; /*Flawfinder: ignore*/
+ temp += ((signed char *)readbuffer)[i*4+3]; /*Flawfinder: ignore*/
+ temp <<= 8;
+ temp += readbuffer[i*4];
+ temp += readbuffer[i*4+2];
+
+ buffer[0][i] = ((float)temp) / 65536.f;
+ }
+ }
+ else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard")
+ {
+ /* uninterleave samples */
+ for(i=0; i<samples ;i++)
+ {
+ temp = readbuffer[i*2+0];
+ temp += readbuffer[i*2+1];
+ temp -= 256;
+ buffer[0][i] = ((float)temp) / 256.f;
+ }
+ }
+ }
+ else if (num_channels == 1)
+ {
+ if (bytes_per_sample == 2)
+ {
+ for(i=0; i < samples ;i++)
+ {
+ temp = ((signed char*)readbuffer)[i*2+1];
+ temp <<= 8;
+ temp += readbuffer[i*2];
+ buffer[0][i] = ((float)temp) / 32768.f;
+ }
+ }
+ else // presume it's 1 byte per which is unsigned (F#@%ing wav "standard")
+ {
+ for(i=0; i < samples ;i++)
+ {
+ temp = readbuffer[i];
+ temp -= 128;
+ buffer[0][i] = ((float)temp) / 128.f;
+ }
+ }
+ }
+
+ /* tell the library how much we actually submitted */
+ vorbis_analysis_wrote(&vd,i);
+ }
+
+ /* vorbis does some data preanalysis, then divvies up blocks for
+ more involved (potentially parallel) processing. Get a single
+ block for encoding now */
+ while(vorbis_analysis_blockout(&vd,&vb)==1)
+ {
+
+ /* analysis */
+ /* Do the main analysis, creating a packet */
+ vorbis_analysis(&vb, NULL);
+ vorbis_bitrate_addblock(&vb);
+
+ while(vorbis_bitrate_flushpacket(&vd, &op))
+ {
+
+ /* weld the packet into the bitstream */
+ ogg_stream_packetin(&os,&op);
+
+ /* write out pages (if any) */
+ while(!eos)
+ {
+ result = ogg_stream_pageout(&os,&og);
+
+ if(result==0)
+ break;
+
+ outfile.write(og.header, og.header_len);
+ outfile.write(og.body, og.body_len);
+
+ /* this could be set above, but for illustrative purposes, I do
+ it here (to show that vorbis does know where the stream ends) */
+
+ if(ogg_page_eos(&og))
+ eos=1;
+
+ }
+ }
+ }
+ }
+
+
+
+ /* clean up and exit. vorbis_info_clear() must be called last */
+
+ ogg_stream_clear(&os);
+ vorbis_block_clear(&vb);
+ vorbis_dsp_clear(&vd);
+ vorbis_comment_clear(&vc);
+ vorbis_info_clear(&vi);
+
+ /* ogg_page and ogg_packet structs always point to storage in
+ libvorbis. They're never freed or manipulated directly */
+
+// fprintf(stderr,"Vorbis encoding: Done.\n");
+ LL_INFOS() << "Vorbis encoding: Done." << LL_ENDL;
+
+#endif
+ return(LLVORBISENC_NOERR);
+
+}
|