From e2e37cced861b98de8c1a7c9c0d3a50d2d90e433 Mon Sep 17 00:00:00 2001 From: Ansariel Date: Wed, 22 May 2024 21:25:21 +0200 Subject: Fix line endlings --- indra/llaudio/llaudiodecodemgr.cpp | 1602 ++++++++++++++++---------------- indra/llaudio/llaudiodecodemgr.h | 108 +-- indra/llaudio/llaudioengine.h | 972 +++++++++---------- indra/llaudio/llaudioengine_openal.cpp | 1120 +++++++++++----------- indra/llaudio/llaudioengine_openal.h | 216 ++--- indra/llaudio/lllistener_openal.h | 118 +-- indra/llaudio/llvorbisencode.cpp | 1012 ++++++++++---------- 7 files changed, 2574 insertions(+), 2574 deletions(-) (limited to 'indra/llaudio') diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp index 602d360f1c..b086e49ba4 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 -#include - -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 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 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& decode_state); - void checkDecodesFinished(); - - protected: - std::deque mDecodeQueue; - std::map> 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 beginDecodingAndWritingAudio(const LLUUID &decode_id); - -// Return true if finished -bool tryFinishAudio(const LLUUID &decode_id, LLPointer 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(NULL); - bool posted = main_queue->postTo( - general_queue, - [decode_id]() // Work done on general queue - { - LLPointer 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 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 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 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& 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& 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 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 +#include + +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 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 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& decode_state); + void checkDecodesFinished(); + + protected: + std::deque mDecodeQueue; + std::map> 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 beginDecodingAndWritingAudio(const LLUUID &decode_id); + +// Return true if finished +bool tryFinishAudio(const LLUUID &decode_id, LLPointer 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(NULL); + bool posted = main_queue->postTo( + general_queue, + [decode_id]() // Work done on general queue + { + LLPointer 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 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 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 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& 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& 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 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 17fa31ee53..79f8b8e92e 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 LLPointer; -class LLVorbisDecodeState; - -class LLAudioDecodeMgr : public LLSingleton -{ - 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 LLPointer; +class LLVorbisDecodeState; + +class LLAudioDecodeMgr : public LLSingleton +{ + 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 9949b8f337..a9a229c0a5 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 -#include -#include - -#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 source_map; - typedef std::map data_map; - - source_map mAllSources; - data_map mAllData; - - std::array 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 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 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 +#include +#include + +#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 source_map; + typedef std::map data_map; + + source_map mAllSources; + data_map mAllData; + + std::array 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 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 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_openal.cpp b/indra/llaudio/llaudioengine_openal.cpp index 9e62e9bfec..18d682b554 100644 --- a/indra/llaudio/llaudioengine_openal.cpp +++ b/indra/llaudio/llaudioengine_openal.cpp @@ -1,560 +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(); - - // 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: "<; - - 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; - } -} - +/** + * @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: "<; + + 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 6875bcc68b..574bec416d 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 F32 WIND_SAMPLE_T; - LLWindGen *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 +/** + * @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 *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_openal.h b/indra/llaudio/lllistener_openal.h index ca5eba8b4e..f1b69ddcef 100644 --- a/indra/llaudio/lllistener_openal.h +++ b/indra/llaudio/lllistener_openal.h @@ -1,59 +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" -#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 - +/** + * @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/llvorbisencode.cpp b/indra/llaudio/llvorbisencode.cpp index 908175038e..83e7fad92f 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 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