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 ++++++++++++++++++------------------ 1 file changed, 801 insertions(+), 801 deletions(-) (limited to 'indra/llaudio/llaudiodecodemgr.cpp') 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; +} -- cgit v1.2.3