diff options
Diffstat (limited to 'indra/llaudio/llaudiodecodemgr.cpp')
-rw-r--r-- | indra/llaudio/llaudiodecodemgr.cpp | 616 |
1 files changed, 616 insertions, 0 deletions
diff --git a/indra/llaudio/llaudiodecodemgr.cpp b/indra/llaudio/llaudiodecodemgr.cpp new file mode 100644 index 0000000000..a819a9fa03 --- /dev/null +++ b/indra/llaudio/llaudiodecodemgr.cpp @@ -0,0 +1,616 @@ +/** + * @file llaudiodecodemgr.cpp + * + * Copyright (c) 2003-$CurrentYear$, Linden Research, Inc. + * $License$ + */ + +#include "linden_common.h" + +#include <vector> +#include <iterator> +#include <algorithm> +#include <stdio.h> + +#include "llaudiodecodemgr.h" + +#include "vorbisdecode.h" +#include "audioengine.h" +#include "lllfsthread.h" +#include "llvfile.h" +#include "llstring.h" +#include "lldir.h" +#include "llendianswizzle.h" +#include "audioengine.h" +#include "llassetstorage.h" + +#include "vorbis/codec.h" +#include "vorbis/vorbisfile.h" + +extern LLAudioEngine *gAudiop; + +LLAudioDecodeMgr *gAudioDecodeMgrp = NULL; + +const S32 wav_header_size = 44; + +class LLVorbisDecodeState +{ +public: + LLVorbisDecodeState(const LLUUID &uuid, const LLString &out_filename); + virtual ~LLVorbisDecodeState(); + + BOOL initDecode(); + BOOL decodeSection(); // Return TRUE if done. + BOOL finishDecode(); + + void flushBadFile(); + + BOOL isValid() const { return mValid; } + BOOL isDone() const { return mDone; } + const LLUUID &getUUID() const { return mUUID; } +protected: + BOOL mValid; + BOOL mDone; + LLUUID mUUID; + + std::vector<U8> mWAVBuffer; +#if !defined(USE_WAV_VFILE) + LLString mOutFilename; + LLLFSThread::handle_t mFileHandle; +#endif + + LLVFile *mInFilep; + OggVorbis_File mVF; + S32 mCurrentSection; +}; + +void LLVorbisDecodeState::flushBadFile() +{ + if (mInFilep) + { + llwarns << "Flushing bad vorbis file from VFS for " << mUUID << llendl; + mInFilep->remove(); + } +} + + +LLAudioDecodeMgr::LLAudioDecodeMgr() +{ + mCurrentDecodep = NULL; +} + +LLAudioDecodeMgr::~LLAudioDecodeMgr() +{ + delete mCurrentDecodep; + mCurrentDecodep = NULL; +} + + +void LLAudioDecodeMgr::processQueue(const F32 num_secs) +{ + LLUUID uuid; + + LLTimer decode_timer; + + BOOL done = FALSE; + while (!done) + { + if (mCurrentDecodep) + { + BOOL res; + + // Decode in a loop until we're done or have run out of time. + while(!(res = mCurrentDecodep->decodeSection()) && (decode_timer.getElapsedTimeF32() < num_secs)) + { + // decodeSection does all of the work above + } + + if (mCurrentDecodep->isDone() && !mCurrentDecodep->isValid()) + { + // We had an error when decoding, abort. + llwarns << mCurrentDecodep->getUUID() << " has invalid vorbis data, aborting decode" << llendl; + mCurrentDecodep->flushBadFile(); + LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID()); + adp->setHasValidData(FALSE); + delete mCurrentDecodep; + mCurrentDecodep = NULL; + done = TRUE; + } + + if (!res) + { + // We've used up out time slice, bail... + done = TRUE; + } + else if (mCurrentDecodep) + { + if (mCurrentDecodep->finishDecode()) + { + // We finished! + if (mCurrentDecodep->isValid() && mCurrentDecodep->isDone()) + { + LLAudioData *adp = gAudiop->getAudioData(mCurrentDecodep->getUUID()); + adp->setHasDecodedData(TRUE); + adp->setHasValidData(TRUE); + + // At this point, we could see if anyone needs this sound immediately, but + // I'm not sure that there's a reason to - we need to poll all of the playing + // sounds anyway. + //llinfos << "Finished the vorbis decode, now what?" << llendl; + } + else + { + llinfos << "Vorbis decode failed!!!" << llendl; + } + delete mCurrentDecodep; + mCurrentDecodep = NULL; + } + done = TRUE; // done for now + } + } + + if (!done) + { + if (!mDecodeQueue.getLength()) + { + // Nothing else on the queue. + done = TRUE; + } + else + { + LLUUID uuid; + mDecodeQueue.pop(uuid); + if (gAudiop->hasDecodedFile(uuid)) + { + // This file has already been decoded, don't decode it again. + continue; + } + + lldebugs << "Decoding " << uuid << " from audio queue!" << llendl; + + char uuid_str[64]; /*Flawfinder: ignore*/ + char d_path[LL_MAX_PATH]; /*Flawfinder: ignore*/ + + LLTimer timer; + timer.reset(); + + uuid.toString(uuid_str); + snprintf(d_path, LL_MAX_PATH, "%s.dsf", gDirUtilp->getExpandedFilename(LL_PATH_CACHE,uuid_str).c_str()); /*Flawfinder: ignore*/ + + mCurrentDecodep = new LLVorbisDecodeState(uuid, d_path); + if (!mCurrentDecodep->initDecode()) + { + delete mCurrentDecodep; + mCurrentDecodep = NULL; + } + } + } + } +} + + +BOOL LLAudioDecodeMgr::addDecodeRequest(const LLUUID &uuid) +{ + if (gAudiop->hasDecodedFile(uuid)) + { + // Already have a decoded version, don't need to decode it. + return TRUE; + } + + if (gAssetStorage->hasLocalAsset(uuid, LLAssetType::AT_SOUND)) + { + // Just put it on the decode queue. + gAudioDecodeMgrp->mDecodeQueue.push(uuid); + return TRUE; + } + + return FALSE; +} + + +S32 LLAudioDecodeMgr::getRequestCount() +{ + /* + S32 count = 0; + if (mCurrentTransfer.notNull()) + { + count++; + } + + count += mRequestQueue.getLength(); + return count; + */ + return 0; +} + + + + + + + + +size_t vfs_read(void *ptr, size_t size, size_t nmemb, void *datasource) +{ + LLVFile *file = (LLVFile *)datasource; + + if (file->read((U8*)ptr, (S32)(size * nmemb))) /*Flawfinder: ignore*/ + { + S32 read = file->getLastBytesRead(); + return read / size; /*Flawfinder: ignore*/ + } + else + { + return 0; + } +} + +int vfs_seek(void *datasource, ogg_int64_t offset, int whence) +{ + LLVFile *file = (LLVFile *)datasource; + + // vfs 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: + llerrs << "Invalid whence argument to vfs_seek" << llendl; + return -1; + } + + if (file->seek((S32)offset, origin)) + { + return 0; + } + else + { + return -1; + } +} + +int vfs_close (void *datasource) +{ + LLVFile *file = (LLVFile *)datasource; + + delete file; + + return 0; +} + +long vfs_tell (void *datasource) +{ + LLVFile *file = (LLVFile *)datasource; + + return file->tell(); +} + + + + +LLVorbisDecodeState::LLVorbisDecodeState(const LLUUID &uuid, const LLString &out_filename) +{ + mDone = FALSE; + mValid = FALSE; + mUUID = uuid; + mInFilep = NULL; + mCurrentSection = 0; +#if !defined(USE_WAV_VFILE) + mOutFilename = out_filename; + mFileHandle = LLLFSThread::nullHandle(); +#endif + // No default value for mVF, it's an ogg structure? +} + +LLVorbisDecodeState::~LLVorbisDecodeState() +{ + if (!mDone) + { + delete mInFilep; + mInFilep = NULL; + } +} + + +BOOL LLVorbisDecodeState::initDecode() +{ + ov_callbacks vfs_callbacks; + vfs_callbacks.read_func = vfs_read; + vfs_callbacks.seek_func = vfs_seek; + vfs_callbacks.close_func = vfs_close; + vfs_callbacks.tell_func = vfs_tell; + + //llinfos << "Initing decode from vfile: " << mUUID << llendl; + + mInFilep = new LLVFile(gVFS, mUUID, LLAssetType::AT_SOUND); + if (!mInFilep || !mInFilep->getSize()) + { + llwarns << "unable to open vorbis source vfile for reading" << llendl; + delete mInFilep; + mInFilep = NULL; + return FALSE; + } + + int r = ov_open_callbacks(mInFilep, &mVF, NULL, 0, vfs_callbacks); + if(r < 0) + { + llwarns << r << " Input to vorbis decode does not appear to be an Ogg bitstream: " << mUUID << llendl; + return(FALSE); + } + + size_t size_guess = (size_t)ov_pcm_total(&mVF, -1); + vorbis_info* vi = ov_info(&mVF, -1); + size_guess *= vi->channels; + size_guess *= 2; + size_guess += 2048; + mWAVBuffer.reserve(size_guess); + mWAVBuffer.resize(wav_header_size); + + { + // 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) + { + llwarns << "No VFS file to decode in vorbis!" << llendl; + return TRUE; + } + if (mDone) + { +// llwarns << "Already done with decode, aborting!" << llendl; + 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; +// llinfos << "Vorbis EOF" << llendl; + } + 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. */ + + llwarns << "BAD vorbis decode in decodeSection." << llendl; + + mValid = FALSE; + mDone = TRUE; + // We're done, return TRUE. + return TRUE; + } + else + { +// llinfos << "Vorbis read " << ret << "bytes" << llendl; + /* 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()) + { + llwarns << "Bogus vorbis decode state for " << getUUID() << ", aborting!" << llendl; + return TRUE; // We've finished + } + +#if !defined(USE_WAV_VFILE) + if (mFileHandle == LLLFSThread::nullHandle()) +#endif + { + 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; + + // + // FUCK!!! 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 (sizeof(mWAVBuffer) >= (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)) < sizeof(mWAVBuffer)) + { + memcpy(&mWAVBuffer[wav_header_size], pcmout, (2 * fade_length)); /*Flawfinder: ignore*/ + } + S32 near_end = mWAVBuffer.size() - (2 * fade_length); + if (sizeof(mWAVBuffer) >= ( 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) < sizeof(mWAVBuffer)) + { + memcpy(&mWAVBuffer[near_end], pcmout, (2 * fade_length));/*Flawfinder: ignore*/ + } + } + + if (36 == data_length) + { + llwarns << "BAD Vorbis decode in finishDecode!" << llendl; + mValid = FALSE; + return TRUE; // we've finished + } +#if !defined(USE_WAV_VFILE) + mFileHandle = LLLFSThread::sLocal->write(mOutFilename, &mWAVBuffer[0], 0, data_length); +#endif + } + + if (mFileHandle != LLLFSThread::nullHandle()) + { + LLLFSThread::status_t s = LLLFSThread::sLocal->getRequestStatus(mFileHandle); + if (s != LLLFSThread::STATUS_COMPLETE) + { + if (s != LLLFSThread::STATUS_QUEUED && s != LLLFSThread::STATUS_INPROGRESS) + { + llerrs << "Bad file status in LLVorbisDecodeState::finishDecode: " << s << llendl; + } + return FALSE; // not finished + } + else + { + LLLFSThread::Request* req = (LLLFSThread::Request*)LLLFSThread::sLocal->getRequest(mFileHandle); + if (req->getBytesRead() == 0) //!= req->getBytes() // should be safe, but needs testing + { + llwarns << "Unable to write file in LLVorbisDecodeState::finishDecode" << llendl; + mValid = FALSE; + return TRUE; // we've finished + } + } + LLLFSThread::sLocal->completeRequest(mFileHandle); + } + + mDone = TRUE; + +#if defined(USE_WAV_VFILE) + // write the data. + LLVFile output(gVFS, mUUID, LLAssetType::AT_SOUND_WAV); + output.write(&mWAVBuffer[0], mWAVBuffer.size()); +#endif + //llinfos << "Finished decode for " << getUUID() << llendl; + + return TRUE; +} |