diff options
Diffstat (limited to 'indra/llaudio')
| -rw-r--r-- | indra/llaudio/CMakeLists.txt | 17 | ||||
| -rw-r--r-- | indra/llaudio/llaudioengine_fmodstudio.cpp | 756 | ||||
| -rw-r--r-- | indra/llaudio/llaudioengine_fmodstudio.h | 131 | ||||
| -rw-r--r-- | indra/llaudio/lllistener_fmodstudio.cpp | 136 | ||||
| -rw-r--r-- | indra/llaudio/lllistener_fmodstudio.h | 65 | ||||
| -rw-r--r-- | indra/llaudio/lllistener_openal.h | 6 | ||||
| -rw-r--r-- | indra/llaudio/llstreamingaudio_fmodstudio.cpp | 481 | ||||
| -rw-r--r-- | indra/llaudio/llstreamingaudio_fmodstudio.h | 76 | 
8 files changed, 1668 insertions, 0 deletions
| diff --git a/indra/llaudio/CMakeLists.txt b/indra/llaudio/CMakeLists.txt index 9278d3c488..f36da83c69 100644 --- a/indra/llaudio/CMakeLists.txt +++ b/indra/llaudio/CMakeLists.txt @@ -4,6 +4,7 @@ project(llaudio)  include(00-Common)  include(LLAudio) +include(FMODSTUDIO)  include(OPENAL)  include(LLCommon) @@ -24,6 +25,20 @@ set(llaudio_HEADER_FILES      llwindgen.h      ) +if (TARGET ll::fmodstudio) +    list(APPEND llaudio_SOURCE_FILES +         llaudioengine_fmodstudio.cpp +         lllistener_fmodstudio.cpp +         llstreamingaudio_fmodstudio.cpp +         ) + +    list(APPEND llaudio_HEADER_FILES +         llaudioengine_fmodstudio.h +         lllistener_fmodstudio.h +         llstreamingaudio_fmodstudio.h +         ) +endif () +  if (TARGET ll::openal)    list(APPEND llaudio_SOURCE_FILES      llaudioengine_openal.cpp @@ -54,3 +69,5 @@ endif()  if( TARGET ll::fmodstudio )      target_link_libraries( llaudio ll::fmodstudio )  endif() + +include(LibraryInstall) diff --git a/indra/llaudio/llaudioengine_fmodstudio.cpp b/indra/llaudio/llaudioengine_fmodstudio.cpp new file mode 100644 index 0000000000..b4e8407083 --- /dev/null +++ b/indra/llaudio/llaudioengine_fmodstudio.cpp @@ -0,0 +1,756 @@ +/** + * @file audioengine_fmodstudio.cpp + * @brief Implementation of LLAudioEngine class abstracting the audio + * support as a FMODSTUDIO implementation + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llstreamingaudio.h" +#include "llstreamingaudio_fmodstudio.h" + +#include "llaudioengine_fmodstudio.h" +#include "lllistener_fmodstudio.h" + +#include "llerror.h" +#include "llmath.h" +#include "llrand.h" + +#include "fmodstudio/fmod.hpp" +#include "fmodstudio/fmod_errors.h" +#include "lldir.h" +#include "llapr.h" + +#include "sound_ids.h" + +FMOD_RESULT F_CALLBACK windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels); + +FMOD::ChannelGroup *LLAudioEngine_FMODSTUDIO::mChannelGroups[LLAudioEngine::AUDIO_TYPE_COUNT] = {0}; + +LLAudioEngine_FMODSTUDIO::LLAudioEngine_FMODSTUDIO(bool enable_profiler) +:   mInited(false), +    mWindGen(NULL), +    mWindDSP(NULL), +    mSystem(NULL), +    mEnableProfiler(enable_profiler), +    mWindDSPDesc(NULL) +{ +} + + +LLAudioEngine_FMODSTUDIO::~LLAudioEngine_FMODSTUDIO() +{ +    // mWindDSPDesc, mWindGen and mWindDSP get cleaned up on cleanupWind in LLAudioEngine::shutdown() +    // mSystem gets cleaned up at shutdown() +} + + +static inline bool Check_FMOD_Error(FMOD_RESULT result, const char *string) +{ +    if (result == FMOD_OK) +        return false; +    LL_DEBUGS("FMOD") << string << " Error: " << FMOD_ErrorString(result) << LL_ENDL; +    return true; +} + +bool LLAudioEngine_FMODSTUDIO::init(void* userdata, const std::string &app_title) +{ +    U32 version; +    FMOD_RESULT result; + +    LL_DEBUGS("AppInit") << "LLAudioEngine_FMODSTUDIO::init() initializing FMOD" << LL_ENDL; + +    result = FMOD::System_Create(&mSystem); +    if (Check_FMOD_Error(result, "FMOD::System_Create")) +        return false; + +    //will call LLAudioEngine_FMODSTUDIO::allocateListener, which needs a valid mSystem pointer. +    LLAudioEngine::init(userdata, app_title); + +    result = mSystem->getVersion(&version); +    Check_FMOD_Error(result, "FMOD::System::getVersion"); + +    if (version < FMOD_VERSION) +    { +        LL_WARNS("AppInit") << "FMOD Studio version mismatch, actual: " << version +            << " expected:" << FMOD_VERSION << LL_ENDL; +    } + +    // In this case, all sounds, PLUS wind and stream will be software. +    result = mSystem->setSoftwareChannels(LL_MAX_AUDIO_CHANNELS + 2); +    Check_FMOD_Error(result, "FMOD::System::setSoftwareChannels"); + +    FMOD_ADVANCEDSETTINGS settings; +    memset(&settings, 0, sizeof(settings)); +    settings.cbSize = sizeof(FMOD_ADVANCEDSETTINGS); +    settings.resamplerMethod = FMOD_DSP_RESAMPLER_LINEAR; + +    result = mSystem->setAdvancedSettings(&settings); +    Check_FMOD_Error(result, "FMOD::System::setAdvancedSettings"); + +    // FMOD_INIT_THREAD_UNSAFE Disables thread safety for API calls. +    // Only use this if FMOD is being called from a single thread, and if Studio API is not being used. +    U32 fmod_flags = FMOD_INIT_NORMAL | FMOD_INIT_3D_RIGHTHANDED | FMOD_INIT_THREAD_UNSAFE; +    if (mEnableProfiler) +    { +        fmod_flags |= FMOD_INIT_PROFILE_ENABLE; +    } + +#if LL_LINUX +    bool audio_ok = false; + +    if (!audio_ok) +    { +        const char* env_string = getenv("LL_BAD_FMOD_PULSEAUDIO"); +        if (NULL == env_string) +        { +            LL_DEBUGS("AppInit") << "Trying PulseAudio audio output..." << LL_ENDL; +            if (mSystem->setOutput(FMOD_OUTPUTTYPE_PULSEAUDIO) == FMOD_OK && +                (result = mSystem->init(LL_MAX_AUDIO_CHANNELS + 2, fmod_flags, const_cast<char*>(app_title.c_str()))) == FMOD_OK) +            { +                LL_DEBUGS("AppInit") << "PulseAudio output initialized OKAY" << LL_ENDL; +                audio_ok = true; +            } +            else +            { +                Check_FMOD_Error(result, "PulseAudio audio output FAILED to initialize"); +            } +        } +        else +        { +            LL_DEBUGS("AppInit") << "PulseAudio audio output SKIPPED" << LL_ENDL; +        } +    } +    if (!audio_ok) +    { +        const char* env_string = getenv("LL_BAD_FMOD_ALSA"); +        if (NULL == env_string) +        { +            LL_DEBUGS("AppInit") << "Trying ALSA audio output..." << LL_ENDL; +            if (mSystem->setOutput(FMOD_OUTPUTTYPE_ALSA) == FMOD_OK && +                (result = mSystem->init(LL_MAX_AUDIO_CHANNELS + 2, fmod_flags, 0)) == FMOD_OK) +            { +                LL_DEBUGS("AppInit") << "ALSA audio output initialized OKAY" << LL_ENDL; +                audio_ok = true; +            } +            else +            { +                Check_FMOD_Error(result, "ALSA audio output FAILED to initialize"); +            } +        } +        else +        { +            LL_DEBUGS("AppInit") << "ALSA audio output SKIPPED" << LL_ENDL; +        } +    } +    if (!audio_ok) +    { +        LL_WARNS("AppInit") << "Overall audio init failure." << LL_ENDL; +        return false; +    } + +    // We're interested in logging which output method we +    // ended up with, for QA purposes. +    FMOD_OUTPUTTYPE output_type; +    mSystem->getOutput(&output_type); +    switch (output_type) +    { +    case FMOD_OUTPUTTYPE_NOSOUND: +        LL_INFOS("AppInit") << "Audio output: NoSound" << LL_ENDL; break; +    case FMOD_OUTPUTTYPE_PULSEAUDIO: +        LL_INFOS("AppInit") << "Audio output: PulseAudio" << LL_ENDL; break; +    case FMOD_OUTPUTTYPE_ALSA: +        LL_INFOS("AppInit") << "Audio output: ALSA" << LL_ENDL; break; +    default: +        LL_INFOS("AppInit") << "Audio output: Unknown!" << LL_ENDL; break; +    }; +#else // LL_LINUX + +    // initialize the FMOD engine +    // number of channel in this case looks to be identiacal to number of max simultaneously +    // playing objects and we can set practically any number +    result = mSystem->init(LL_MAX_AUDIO_CHANNELS + 2, fmod_flags, 0); +    if (Check_FMOD_Error(result, "Error initializing FMOD Studio with default settins, retrying with other format")) +    { +        result = mSystem->setSoftwareFormat(44100, FMOD_SPEAKERMODE_STEREO, 0/*- ignore*/); +        if (Check_FMOD_Error(result, "Error setting sotware format. Can't init.")) +        { +            return false; +        } +        result = mSystem->init(LL_MAX_AUDIO_CHANNELS + 2, fmod_flags, 0); +    } +    if (Check_FMOD_Error(result, "Error initializing FMOD Studio")) +    { +        // If it fails here and (result == FMOD_ERR_OUTPUT_CREATEBUFFER), +        // we can retry with other settings +        return false; +    } +#endif + +    LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init() FMOD Studio initialized correctly" << LL_ENDL; + +    int r_numbuffers, r_samplerate, r_channels; +    unsigned int r_bufferlength; +    char r_name[512]; +    int latency = 100; +    mSystem->getDSPBufferSize(&r_bufferlength, &r_numbuffers); +    LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_bufferlength=" << r_bufferlength << " bytes" << LL_ENDL; +    LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_numbuffers=" << r_numbuffers << LL_ENDL; + +    mSystem->getDriverInfo(0, r_name, 511, NULL, &r_samplerate, NULL, &r_channels); +    r_name[511] = '\0'; +    LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): r_name=\"" << r_name << "\"" << LL_ENDL; + +    if (r_samplerate != 0) +        latency = (int)(1000.0f * r_bufferlength * r_numbuffers / r_samplerate); +    LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): latency=" << latency << "ms" << LL_ENDL; + +    mInited = true; + +    LL_INFOS("AppInit") << "LLAudioEngine_FMODSTUDIO::init(): initialization complete." << LL_ENDL; + +    return true; +} + + +std::string LLAudioEngine_FMODSTUDIO::getDriverName(bool verbose) +{ +    llassert_always(mSystem); +    if (verbose) +    { +        U32 version; +        if (!Check_FMOD_Error(mSystem->getVersion(&version), "FMOD::System::getVersion")) +        { +            return llformat("FMOD Studio %1x.%02x.%02x (Firelight Technologies Pty Ltd)", version >> 16, version >> 8 & 0x000000FF, version & 0x000000FF); +        } +    } +    return "FMOD STUDIO"; +} + + +// create our favourite FMOD-native streaming audio implementation +LLStreamingAudioInterface *LLAudioEngine_FMODSTUDIO::createDefaultStreamingAudioImpl() const +{ +    return new LLStreamingAudio_FMODSTUDIO(mSystem); +} + + +void LLAudioEngine_FMODSTUDIO::allocateListener(void) +{ +    mListenerp = (LLListener *) new LLListener_FMODSTUDIO(mSystem); +    if (!mListenerp) +    { +        LL_WARNS("FMOD") << "Listener creation failed" << LL_ENDL; +    } +} + + +void LLAudioEngine_FMODSTUDIO::shutdown() +{ +    stopInternetStream(); + +    LL_INFOS("FMOD") << "About to LLAudioEngine::shutdown()" << LL_ENDL; +    LLAudioEngine::shutdown(); + +    LL_INFOS("FMOD") << "LLAudioEngine_FMODSTUDIO::shutdown() closing FMOD Studio" << LL_ENDL; +    if (mSystem) +    { +        mSystem->close(); +        mSystem->release(); +    } +    LL_INFOS("FMOD") << "LLAudioEngine_FMODSTUDIO::shutdown() done closing FMOD Studio" << LL_ENDL; + +    delete mListenerp; +    mListenerp = NULL; +} + + +LLAudioBuffer * LLAudioEngine_FMODSTUDIO::createBuffer() +{ +    return new LLAudioBufferFMODSTUDIO(mSystem); +} + + +LLAudioChannel * LLAudioEngine_FMODSTUDIO::createChannel() +{ +    return new LLAudioChannelFMODSTUDIO(mSystem); +} + +bool LLAudioEngine_FMODSTUDIO::initWind() +{ +    mNextWindUpdate = 0.0; + +    if (!mWindDSPDesc) +    { +        mWindDSPDesc = new FMOD_DSP_DESCRIPTION(); +    } + +    if (!mWindDSP) +    { +        memset(mWindDSPDesc, 0, sizeof(*mWindDSPDesc)); //Set everything to zero +        strncpy(mWindDSPDesc->name, "Wind Unit", sizeof(mWindDSPDesc->name)); +        mWindDSPDesc->pluginsdkversion = FMOD_PLUGIN_SDK_VERSION; +        mWindDSPDesc->read = &windCallback; // Assign callback - may be called from arbitrary threads +        if (Check_FMOD_Error(mSystem->createDSP(mWindDSPDesc, &mWindDSP), "FMOD::createDSP")) +            return false; + +        if (mWindGen) +            delete mWindGen; + +        int frequency = 44100; + +        FMOD_SPEAKERMODE mode; +        if (Check_FMOD_Error(mSystem->getSoftwareFormat(&frequency, &mode, nullptr), "FMOD::System::getSoftwareFormat")) +        { +            cleanupWind(); +            return false; +        } + +        mWindGen = new LLWindGen<MIXBUFFERFORMAT>((U32)frequency); + +        if (Check_FMOD_Error(mWindDSP->setUserData((void*)mWindGen), "FMOD::DSP::setUserData")) +        { +            cleanupWind(); +            return false; +        } +        if (Check_FMOD_Error(mWindDSP->setChannelFormat(FMOD_CHANNELMASK_STEREO, 2, mode), "FMOD::DSP::setChannelFormat")) +        { +            cleanupWind(); +            return false; +        } +    } + +    // *TODO:  Should this guard against multiple plays? +    if (Check_FMOD_Error(mSystem->playDSP(mWindDSP, nullptr, false, nullptr), "FMOD::System::playDSP")) +    { +        cleanupWind(); +        return false; +    } +    return true; +} + + +void LLAudioEngine_FMODSTUDIO::cleanupWind() +{ +    if (mWindDSP) +    { +        FMOD::ChannelGroup* master_group = NULL; +        if (!Check_FMOD_Error(mSystem->getMasterChannelGroup(&master_group), "FMOD::System::getMasterChannelGroup") +            && master_group) +        { +            master_group->removeDSP(mWindDSP); +        } +        mWindDSP->release(); +        mWindDSP = NULL; +    } + +    delete mWindDSPDesc; +    mWindDSPDesc = NULL; + +    delete mWindGen; +    mWindGen = NULL; +} + + +//----------------------------------------------------------------------- +void LLAudioEngine_FMODSTUDIO::updateWind(LLVector3 wind_vec, F32 camera_height_above_water) +{ +    LLVector3 wind_pos; +    F64 pitch; +    F64 center_freq; + +    if (!mEnableWind) +    { +        return; +    } + +    if (mWindUpdateTimer.checkExpirationAndReset(LL_WIND_UPDATE_INTERVAL)) +    { + +        // wind comes in as Linden coordinate (+X = forward, +Y = left, +Z = up) +        // need to convert this to the conventional orientation DS3D and OpenAL use +        // where +X = right, +Y = up, +Z = backwards + +        wind_vec.setVec(-wind_vec.mV[1], wind_vec.mV[2], -wind_vec.mV[0]); + +        // cerr << "Wind update" << endl; + +        pitch = 1.0 + mapWindVecToPitch(wind_vec); +        center_freq = 80.0 * pow(pitch, 2.5*(mapWindVecToGain(wind_vec) + 1.0)); + +        mWindGen->mTargetFreq = (F32)center_freq; +        mWindGen->mTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain; +        mWindGen->mTargetPanGainR = (F32)mapWindVecToPan(wind_vec); +    } +} + +//----------------------------------------------------------------------- +void LLAudioEngine_FMODSTUDIO::setInternalGain(F32 gain) +{ +    if (!mInited) +    { +        return; +    } + +    gain = llclamp(gain, 0.0f, 1.0f); + +    FMOD::ChannelGroup* master_group = NULL; +    if (!Check_FMOD_Error(mSystem->getMasterChannelGroup(&master_group), "FMOD::System::getMasterChannelGroup") +        && master_group) +    { +        master_group->setVolume(gain); +    } + +    LLStreamingAudioInterface *saimpl = getStreamingAudioImpl(); +    if (saimpl) +    { +        // fmod likes its streaming audio channel gain re-asserted after +        // master volume change. +        saimpl->setGain(saimpl->getGain()); +    } +} + +// +// LLAudioChannelFMODSTUDIO implementation +// + +LLAudioChannelFMODSTUDIO::LLAudioChannelFMODSTUDIO(FMOD::System *system) : LLAudioChannel(), mSystemp(system), mChannelp(NULL), mLastSamplePos(0) +{ +} + + +LLAudioChannelFMODSTUDIO::~LLAudioChannelFMODSTUDIO() +{ +    cleanup(); +} + +bool LLAudioChannelFMODSTUDIO::updateBuffer() +{ +    if (!mCurrentSourcep) +    { +        // This channel isn't associated with any source, nothing +        // to be updated +        return false; +    } + +    if (LLAudioChannel::updateBuffer()) +    { +        // Base class update returned true, which means that we need to actually +        // set up the channel for a different buffer. + +        LLAudioBufferFMODSTUDIO *bufferp = (LLAudioBufferFMODSTUDIO *)mCurrentSourcep->getCurrentBuffer(); + +        // Grab the FMOD sample associated with the buffer +        FMOD::Sound *soundp = bufferp->getSound(); +        if (!soundp) +        { +            // This is bad, there should ALWAYS be a sound associated with a legit +            // buffer. +            LL_ERRS() << "No FMOD sound!" << LL_ENDL; +            return false; +        } + + +        // Actually play the sound.  Start it off paused so we can do all the necessary +        // setup. +        if (!mChannelp) +        { +            FMOD_RESULT result = getSystem()->playSound(soundp, NULL /*free channel?*/, true, &mChannelp); +            Check_FMOD_Error(result, "FMOD::System::playSound"); +        } + +        // Setting up channel mChannelID +    } + +    // If we have a source for the channel, we need to update its gain. +    if (mCurrentSourcep) +    { +        // SJB: warnings can spam and hurt framerate, disabling +        //FMOD_RESULT result; + +        mChannelp->setVolume(getSecondaryGain() * mCurrentSourcep->getGain()); +        //Check_FMOD_Error(result, "FMOD::Channel::setVolume"); + +        mChannelp->setMode(mCurrentSourcep->isLoop() ? FMOD_LOOP_NORMAL : FMOD_LOOP_OFF); +        /*if(Check_FMOD_Error(result, "FMOD::Channel::setMode")) +        { +        S32 index; +        mChannelp->getIndex(&index); +        LL_WARNS() << "Channel " << index << "Source ID: " << mCurrentSourcep->getID() +        << " at " << mCurrentSourcep->getPositionGlobal() << LL_ENDL; +        }*/ +    } + +    return true; +} + + +void LLAudioChannelFMODSTUDIO::update3DPosition() +{ +    if (!mChannelp) +    { +        // We're not actually a live channel (i.e., we're not playing back anything) +        return; +    } + +    LLAudioBufferFMODSTUDIO  *bufferp = (LLAudioBufferFMODSTUDIO  *)mCurrentBufferp; +    if (!bufferp) +    { +        // We don't have a buffer associated with us (should really have been picked up +        // by the above if. +        return; +    } + +    if (mCurrentSourcep->isForcedPriority()) +    { +        // Prioritized UI and preview sounds don't need to do any positional updates. +        set3DMode(false); +    } +    else +    { +        // Localized sound.  Update the position and velocity of the sound. +        set3DMode(true); + +        LLVector3 float_pos; +        float_pos.setVec(mCurrentSourcep->getPositionGlobal()); +        FMOD_RESULT result = mChannelp->set3DAttributes((FMOD_VECTOR*)float_pos.mV, (FMOD_VECTOR*)mCurrentSourcep->getVelocity().mV); +        Check_FMOD_Error(result, "FMOD::Channel::set3DAttributes"); +    } +} + + +void LLAudioChannelFMODSTUDIO::updateLoop() +{ +    if (!mChannelp) +    { +        // May want to clear up the loop/sample counters. +        return; +    } + +    // +    // Hack:  We keep track of whether we looped or not by seeing when the +    // sample position looks like it's going backwards.  Not reliable; may +    // yield false negatives. +    // +    U32 cur_pos; +    mChannelp->getPosition(&cur_pos, FMOD_TIMEUNIT_PCMBYTES); + +    if (cur_pos < (U32)mLastSamplePos) +    { +        mLoopedThisFrame = true; +    } +    mLastSamplePos = cur_pos; +} + + +void LLAudioChannelFMODSTUDIO::cleanup() +{ +    if (!mChannelp) +    { +        // Aborting cleanup with no channel handle. +        return; +    } + +    //Cleaning up channel mChannelID +    Check_FMOD_Error(mChannelp->stop(), "FMOD::Channel::stop"); + +    mCurrentBufferp = NULL; +    mChannelp = NULL; +} + + +void LLAudioChannelFMODSTUDIO::play() +{ +    if (!mChannelp) +    { +        LL_WARNS() << "Playing without a channel handle, aborting" << LL_ENDL; +        return; +    } + +    Check_FMOD_Error(mChannelp->setPaused(false), "FMOD::Channel::pause"); + +    getSource()->setPlayedOnce(true); + +    if (LLAudioEngine_FMODSTUDIO::mChannelGroups[getSource()->getType()]) +        mChannelp->setChannelGroup(LLAudioEngine_FMODSTUDIO::mChannelGroups[getSource()->getType()]); +} + + +void LLAudioChannelFMODSTUDIO::playSynced(LLAudioChannel *channelp) +{ +    LLAudioChannelFMODSTUDIO *fmod_channelp = (LLAudioChannelFMODSTUDIO*)channelp; +    if (!(fmod_channelp->mChannelp && mChannelp)) +    { +        // Don't have channels allocated to both the master and the slave +        return; +    } + +    U32 cur_pos; +    if (Check_FMOD_Error(mChannelp->getPosition(&cur_pos, FMOD_TIMEUNIT_PCMBYTES), "Unable to retrieve current position")) +        return; + +    cur_pos %= mCurrentBufferp->getLength(); + +    // Try to match the position of our sync master +    Check_FMOD_Error(mChannelp->setPosition(cur_pos, FMOD_TIMEUNIT_PCMBYTES), "Unable to set current position"); + +    // Start us playing +    play(); +} + + +bool LLAudioChannelFMODSTUDIO::isPlaying() +{ +    if (!mChannelp) +    { +        return false; +    } + +    bool paused, playing; +    mChannelp->getPaused(&paused); +    mChannelp->isPlaying(&playing); +    return !paused && playing; +} + + +// +// LLAudioChannelFMODSTUDIO implementation +// + + +LLAudioBufferFMODSTUDIO::LLAudioBufferFMODSTUDIO(FMOD::System *system) : mSystemp(system), mSoundp(NULL) +{ +} + + +LLAudioBufferFMODSTUDIO::~LLAudioBufferFMODSTUDIO() +{ +    if (mSoundp) +    { +        mSoundp->release(); +        mSoundp = NULL; +    } +} + + +bool LLAudioBufferFMODSTUDIO::loadWAV(const std::string& filename) +{ +    // Try to open a wav file from disk.  This will eventually go away, as we don't +    // really want to block doing this. +    if (filename.empty()) +    { +        // invalid filename, abort. +        return false; +    } + +    if (!gDirUtilp->fileExists(filename)) +    { +        // File not found, abort. +        return false; +    } + +    if (mSoundp) +    { +        // If there's already something loaded in this buffer, clean it up. +        mSoundp->release(); +        mSoundp = NULL; +    } + +    FMOD_MODE base_mode = FMOD_LOOP_NORMAL; +    FMOD_CREATESOUNDEXINFO exinfo; +    memset(&exinfo, 0, sizeof(exinfo)); +    exinfo.cbsize = sizeof(exinfo); +    exinfo.suggestedsoundtype = FMOD_SOUND_TYPE_WAV;    //Hint to speed up loading. +    // Load up the wav file into an fmod sample (since 1.05 fmod studio expects everything in UTF-8) +    FMOD_RESULT result = getSystem()->createSound(filename.c_str(), base_mode, &exinfo, &mSoundp); + +    if (result != FMOD_OK) +    { +        // We failed to load the file for some reason. +        LL_WARNS() << "Could not load data '" << filename << "': " << FMOD_ErrorString(result) << LL_ENDL; + +        // +        // If we EVER want to load wav files provided by end users, we need +        // to rethink this! +        // +        // file is probably corrupt - remove it. +        LLFile::remove(filename); +        return false; +    } + +    // Everything went well, return true +    return true; +} + + +U32 LLAudioBufferFMODSTUDIO::getLength() +{ +    if (!mSoundp) +    { +        return 0; +    } + +    U32 length; +    mSoundp->getLength(&length, FMOD_TIMEUNIT_PCMBYTES); +    return length; +} + + +void LLAudioChannelFMODSTUDIO::set3DMode(bool use3d) +{ +    FMOD_MODE current_mode; +    if (mChannelp->getMode(¤t_mode) != FMOD_OK) +        return; +    FMOD_MODE new_mode = current_mode; +    new_mode &= ~(use3d ? FMOD_2D : FMOD_3D); +    new_mode |= use3d ? FMOD_3D : FMOD_2D; + +    if (current_mode != new_mode) +    { +        mChannelp->setMode(new_mode); +    } +} + +// *NOTE:  This is almost certainly being called on the mixer thread, +// not the main thread.  May have implications for callees or audio +// engine shutdown. + +FMOD_RESULT F_CALLBACK windCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels) +{ +    // inbuffer = fmod's original mixbuffer. +    // outbuffer = the buffer passed from the previous DSP unit. +    // length = length in samples at this mix time. + +    LLWindGen<LLAudioEngine_FMODSTUDIO::MIXBUFFERFORMAT> *windgen = NULL; +    FMOD::DSP *thisdsp = (FMOD::DSP *)dsp_state->instance; + +    thisdsp->getUserData((void **)&windgen); + +    if (windgen) +    { +        windgen->windGenerate((LLAudioEngine_FMODSTUDIO::MIXBUFFERFORMAT *)outbuffer, length); +    } + +    return FMOD_OK; +} diff --git a/indra/llaudio/llaudioengine_fmodstudio.h b/indra/llaudio/llaudioengine_fmodstudio.h new file mode 100644 index 0000000000..eb346e0466 --- /dev/null +++ b/indra/llaudio/llaudioengine_fmodstudio.h @@ -0,0 +1,131 @@ +/** + * @file audioengine_fmodstudio.h + * @brief Definition of LLAudioEngine class abstracting the audio + * support as a FMODSTUDIO implementation + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LL_AUDIOENGINE_FMODSTUDIO_H +#define LL_AUDIOENGINE_FMODSTUDIO_H + +#include "llaudioengine.h" +#include "llwindgen.h" + +//Stubs +class LLAudioStreamManagerFMODSTUDIO; +namespace FMOD +{ +    class System; +    class Channel; +    class ChannelGroup; +    class Sound; +    class DSP; +} +typedef struct FMOD_DSP_DESCRIPTION FMOD_DSP_DESCRIPTION; + +//Interfaces +class LLAudioEngine_FMODSTUDIO : public LLAudioEngine +{ +public: +    LLAudioEngine_FMODSTUDIO(bool enable_profiler); +    virtual ~LLAudioEngine_FMODSTUDIO(); + +    // initialization/startup/shutdown +    virtual bool init(void *user_data, const std::string &app_title); +    virtual std::string getDriverName(bool verbose); +    virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const; +    virtual void allocateListener(); + +    virtual void shutdown(); + +    /*virtual*/ bool initWind(); +    /*virtual*/ void cleanupWind(); + +    /*virtual*/void updateWind(LLVector3 direction, F32 camera_height_above_water); + +    typedef F32 MIXBUFFERFORMAT; + +    FMOD::System *getSystem()               const {return mSystem;} +protected: +    /*virtual*/ LLAudioBuffer *createBuffer(); // Get a free buffer, or flush an existing one if you have to. +    /*virtual*/ LLAudioChannel *createChannel(); // Create a new audio channel. + +    /*virtual*/ void setInternalGain(F32 gain); + +    bool mInited; + +    LLWindGen<MIXBUFFERFORMAT> *mWindGen; + +    FMOD_DSP_DESCRIPTION *mWindDSPDesc; +    FMOD::DSP *mWindDSP; +    FMOD::System *mSystem; +    bool mEnableProfiler; + +public: +    static FMOD::ChannelGroup *mChannelGroups[LLAudioEngine::AUDIO_TYPE_COUNT]; +}; + + +class LLAudioChannelFMODSTUDIO : public LLAudioChannel +{ +public: +    LLAudioChannelFMODSTUDIO(FMOD::System *audioengine); +    virtual ~LLAudioChannelFMODSTUDIO(); + +protected: +    /*virtual*/ void play(); +    /*virtual*/ void playSynced(LLAudioChannel *channelp); +    /*virtual*/ void cleanup(); +    /*virtual*/ bool isPlaying(); + +    /*virtual*/ bool updateBuffer(); +    /*virtual*/ void update3DPosition(); +    /*virtual*/ void updateLoop(); + +    void set3DMode(bool use3d); +protected: +    FMOD::System *getSystem()   const {return mSystemp;} +    FMOD::System *mSystemp; +    FMOD::Channel *mChannelp; +    S32 mLastSamplePos; +}; + + +class LLAudioBufferFMODSTUDIO : public LLAudioBuffer +{ +public: +    LLAudioBufferFMODSTUDIO(FMOD::System *audioengine); +    virtual ~LLAudioBufferFMODSTUDIO(); + +    /*virtual*/ bool loadWAV(const std::string& filename); +    /*virtual*/ U32 getLength(); +    friend class LLAudioChannelFMODSTUDIO; +protected: +    FMOD::System *getSystem()   const {return mSystemp;} +    FMOD::System *mSystemp; +    FMOD::Sound *getSound()     const{ return mSoundp; } +    FMOD::Sound *mSoundp; +}; + + +#endif // LL_AUDIOENGINE_FMODSTUDIO_H diff --git a/indra/llaudio/lllistener_fmodstudio.cpp b/indra/llaudio/lllistener_fmodstudio.cpp new file mode 100644 index 0000000000..9dc55eec79 --- /dev/null +++ b/indra/llaudio/lllistener_fmodstudio.cpp @@ -0,0 +1,136 @@ +/** + * @file listener_fmodstudio.cpp + * @brief Implementation of LISTENER class abstracting the audio + * support as a FMODSTUDIO implementation + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" +#include "llaudioengine.h" +#include "lllistener_fmodstudio.h" +#include "fmodstudio/fmod.hpp" + +//----------------------------------------------------------------------- +// constructor +//----------------------------------------------------------------------- +LLListener_FMODSTUDIO::LLListener_FMODSTUDIO(FMOD::System *system) +{ +    mSystem = system; +    init(); +} + +//----------------------------------------------------------------------- +LLListener_FMODSTUDIO::~LLListener_FMODSTUDIO() +{ +} + +//----------------------------------------------------------------------- +void LLListener_FMODSTUDIO::init(void) +{ +    // do inherited +    LLListener::init(); +    mDopplerFactor = 1.0f; +    mRolloffFactor = 1.0f; +} + +//----------------------------------------------------------------------- +void LLListener_FMODSTUDIO::translate(LLVector3 offset) +{ +    LLListener::translate(offset); + +    mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV); +} + +//----------------------------------------------------------------------- +void LLListener_FMODSTUDIO::setPosition(LLVector3 pos) +{ +    LLListener::setPosition(pos); + +    mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV); +} + +//----------------------------------------------------------------------- +void LLListener_FMODSTUDIO::setVelocity(LLVector3 vel) +{ +    LLListener::setVelocity(vel); + +    mSystem->set3DListenerAttributes(0, NULL, (FMOD_VECTOR*)mVelocity.mV, (FMOD_VECTOR*)mListenAt.mV, (FMOD_VECTOR*)mListenUp.mV); +} + +//----------------------------------------------------------------------- +void LLListener_FMODSTUDIO::orient(LLVector3 up, LLVector3 at) +{ +    LLListener::orient(up, at); + +    // at = -at; by default Fmod studio is 'left-handed' but we are providing +    // flag FMOD_INIT_3D_RIGHTHANDED so no correction are needed + +    mSystem->set3DListenerAttributes(0, NULL, NULL, (FMOD_VECTOR*)at.mV, (FMOD_VECTOR*)up.mV); +} + +//----------------------------------------------------------------------- +void LLListener_FMODSTUDIO::commitDeferredChanges() +{ +    if (!mSystem) +    { +        return; +    } + +    mSystem->update(); +} + + +void LLListener_FMODSTUDIO::setRolloffFactor(F32 factor) +{ +    //An internal FMOD optimization skips 3D updates if there have not been changes to the 3D sound environment. +    // (this was true for FMODex, looks to be still true for FMOD STUDIO, but needs a recheck) +    //Sadly, a change in rolloff is not accounted for, thus we must touch the listener properties as well. +    //In short: Changing the position ticks a dirtyflag inside fmod, which makes it not skip 3D processing next update call. +    if (mRolloffFactor != factor) +    { +        LLVector3 pos = mPosition - LLVector3(0.f, 0.f, .1f); +        mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)pos.mV, NULL, NULL, NULL); +        mSystem->set3DListenerAttributes(0, (FMOD_VECTOR*)mPosition.mV, NULL, NULL, NULL); +    } +    mRolloffFactor = factor; +    mSystem->set3DSettings(mDopplerFactor, 1.f, mRolloffFactor); +} + + +F32 LLListener_FMODSTUDIO::getRolloffFactor() +{ +    return mRolloffFactor; +} + + +void LLListener_FMODSTUDIO::setDopplerFactor(F32 factor) +{ +    mDopplerFactor = factor; +    mSystem->set3DSettings(mDopplerFactor, 1.f, mRolloffFactor); +} + + +F32 LLListener_FMODSTUDIO::getDopplerFactor() +{ +    return mDopplerFactor; +} diff --git a/indra/llaudio/lllistener_fmodstudio.h b/indra/llaudio/lllistener_fmodstudio.h new file mode 100644 index 0000000000..5287cbedc6 --- /dev/null +++ b/indra/llaudio/lllistener_fmodstudio.h @@ -0,0 +1,65 @@ +/** + * @file listener_fmodstudio.h + * @brief Description of LISTENER class abstracting the audio support + * as an FMOD 3D implementation + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LL_LISTENER_FMODSTUDIO_H +#define LL_LISTENER_FMODSTUDIO_H + +#include "lllistener.h" + +//Stubs +namespace FMOD +{ +    class System; +} + +//Interfaces +class LLListener_FMODSTUDIO : public LLListener +{ +public: +    LLListener_FMODSTUDIO(FMOD::System *system); +    virtual ~LLListener_FMODSTUDIO(); +    virtual void init(); + +    virtual void translate(LLVector3 offset); +    virtual void setPosition(LLVector3 pos); +    virtual void setVelocity(LLVector3 vel); +    virtual void orient(LLVector3 up, LLVector3 at); +    virtual void commitDeferredChanges(); + +    virtual void setDopplerFactor(F32 factor); +    virtual F32 getDopplerFactor(); +    virtual void setRolloffFactor(F32 factor); +    virtual F32 getRolloffFactor(); +protected: +    FMOD::System *mSystem; +    F32 mDopplerFactor; +    F32 mRolloffFactor; +}; + +#endif + + diff --git a/indra/llaudio/lllistener_openal.h b/indra/llaudio/lllistener_openal.h index f1b69ddcef..12d9aa6611 100644 --- a/indra/llaudio/lllistener_openal.h +++ b/indra/llaudio/lllistener_openal.h @@ -31,7 +31,13 @@  #include "lllistener.h"  #include "AL/al.h" +#ifdef LL_DARWIN +#undef __APPLE__ +#endif  #include "AL/alut.h" +#ifdef LL_DARWIN +#define __APPLE__ +#endif  #include "AL/alext.h"  class LLListener_OpenAL  : public LLListener diff --git a/indra/llaudio/llstreamingaudio_fmodstudio.cpp b/indra/llaudio/llstreamingaudio_fmodstudio.cpp new file mode 100644 index 0000000000..22fc86c0cd --- /dev/null +++ b/indra/llaudio/llstreamingaudio_fmodstudio.cpp @@ -0,0 +1,481 @@ +/** + * @file streamingaudio_fmodstudio.cpp + * @brief LLStreamingAudio_FMODSTUDIO implementation + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#include "linden_common.h" + +#include "llmath.h" + +#include "fmodstudio/fmod.hpp" +#include "fmodstudio/fmod_errors.h" + +#include "llstreamingaudio_fmodstudio.h" + + +class LLAudioStreamManagerFMODSTUDIO +{ +public: +    LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, const std::string& url); +    FMOD::Channel* startStream(); +    bool stopStream(); // Returns true if the stream was successfully stopped. +    bool ready(); + +    const std::string& getURL()     { return mInternetStreamURL; } + +    FMOD_OPENSTATE getOpenState(unsigned int* percentbuffered = NULL, bool* starving = NULL, bool* diskbusy = NULL); +protected: +    FMOD::System* mSystem; +    FMOD::Channel* mStreamChannel; +    FMOD::Sound* mInternetStream; +    bool mReady; + +    std::string mInternetStreamURL; +}; + + + +//--------------------------------------------------------------------------- +// Internet Streaming +//--------------------------------------------------------------------------- +LLStreamingAudio_FMODSTUDIO::LLStreamingAudio_FMODSTUDIO(FMOD::System *system) : +mSystem(system), +mCurrentInternetStreamp(NULL), +mFMODInternetStreamChannelp(NULL), +mGain(1.0f), +mRetryCount(0) +{ +    // Number of milliseconds of audio to buffer for the audio card. +    // Must be larger than the usual Second Life frame stutter time. +    const U32 buffer_seconds = 10;      //sec +    const U32 estimated_bitrate = 128;  //kbit/sec +    FMOD_RESULT result = mSystem->setStreamBufferSize(estimated_bitrate * buffer_seconds * 128/*bytes/kbit*/, FMOD_TIMEUNIT_RAWBYTES); +    if (result != FMOD_OK) +    { +        LL_WARNS("FMOD") << "setStreamBufferSize error: " << FMOD_ErrorString(result) << LL_ENDL; +    } + +    // Here's where we set the size of the network buffer and some buffering +    // parameters.  In this case we want a network buffer of 16k, we want it +    // to prebuffer 40% of that when we first connect, and we want it +    // to rebuffer 80% of that whenever we encounter a buffer underrun. + +    // Leave the net buffer properties at the default. +    //FSOUND_Stream_Net_SetBufferProperties(20000, 40, 80); +} + + +LLStreamingAudio_FMODSTUDIO::~LLStreamingAudio_FMODSTUDIO() +{ +    if (mCurrentInternetStreamp) +    { +        // Isn't supposed to hapen, stream should be clear by now, +        // and if it does, we are likely going to crash. +        LL_WARNS("FMOD") << "mCurrentInternetStreamp not null on shutdown!" << LL_ENDL; +        stop(); +    } + +    // Kill dead internet streams, if possible +    killDeadStreams(); + +    if (!mDeadStreams.empty()) +    { +        // LLStreamingAudio_FMODSTUDIO was inited on startup +        // and should be destroyed on shutdown, it should +        // wait for streams to die to not cause crashes or +        // leaks. +        // Ideally we need to wait on some kind of callback +        // to release() streams correctly, but 200 ms should +        // be enough and we can't wait forever. +        LL_INFOS("FMOD") << "Waiting for " << (S32)mDeadStreams.size() << " streams to stop" << LL_ENDL; +        for (S32 i = 0; i < 20; i++) +        { +            const U32 ms_delay = 10; +            ms_sleep(ms_delay); // rude, but not many options here +            killDeadStreams(); +            if (mDeadStreams.empty()) +            { +                LL_INFOS("FMOD") << "All streams stopped after " << (S32)((i + 1) * ms_delay) << "ms" << LL_ENDL; +                break; +            } +        } +    } + +    if (!mDeadStreams.empty()) +    { +        LL_WARNS("FMOD") << "Failed to kill some audio streams" << LL_ENDL; +    } +} + +void LLStreamingAudio_FMODSTUDIO::killDeadStreams() +{ +    std::list<LLAudioStreamManagerFMODSTUDIO *>::iterator iter; +    for (iter = mDeadStreams.begin(); iter != mDeadStreams.end();) +    { +        LLAudioStreamManagerFMODSTUDIO *streamp = *iter; +        if (streamp->stopStream()) +        { +            LL_INFOS("FMOD") << "Closed dead stream" << LL_ENDL; +            delete streamp; +            iter = mDeadStreams.erase(iter); +        } +        else +        { +            iter++; +        } +    } +} + +void LLStreamingAudio_FMODSTUDIO::start(const std::string& url) +{ +    //if (!mInited) +    //{ +    //  LL_WARNS() << "startInternetStream before audio initialized" << LL_ENDL; +    //  return; +    //} + +    // "stop" stream but don't clear url, etc. in case url == mInternetStreamURL +    stop(); + +    if (!url.empty()) +    { +        LL_INFOS("FMOD") << "Starting internet stream: " << url << LL_ENDL; +        mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem, url); +        mURL = url; +    } +    else +    { +        LL_INFOS("FMOD") << "Set internet stream to null" << LL_ENDL; +        mURL.clear(); +    } + +    mRetryCount = 0; +} + + +void LLStreamingAudio_FMODSTUDIO::update() +{ +    // Kill dead internet streams, if possible +    killDeadStreams(); + +    // Don't do anything if there are no streams playing +    if (!mCurrentInternetStreamp) +    { +        return; +    } + +    unsigned int progress; +    bool starving; +    bool diskbusy; +    FMOD_OPENSTATE open_state = mCurrentInternetStreamp->getOpenState(&progress, &starving, &diskbusy); + +    if (open_state == FMOD_OPENSTATE_READY) +    { +        // Stream is live + +        // start the stream if it's ready +        if (!mFMODInternetStreamChannelp && +            (mFMODInternetStreamChannelp = mCurrentInternetStreamp->startStream())) +        { +            // Reset volume to previously set volume +            setGain(getGain()); +            mFMODInternetStreamChannelp->setPaused(false); +        } +        mRetryCount = 0; +    } +    else if (open_state == FMOD_OPENSTATE_ERROR) +    { +        LL_INFOS("FMOD") << "State: FMOD_OPENSTATE_ERROR" +            << " Progress: " << U32(progress) +            << " Starving: " << S32(starving) +            << " Diskbusy: " << S32(diskbusy) << LL_ENDL; +        if (mRetryCount < 2) +        { +            // Retry +            std::string url = mURL; +            stop(); // might drop mURL, drops mCurrentInternetStreamp + +            mRetryCount++; + +            if (!url.empty()) +            { +                LL_INFOS("FMOD") << "Restarting internet stream: " << url  << ", attempt " << (mRetryCount + 1) << LL_ENDL; +                mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem, url); +                mURL = url; +            } +        } +        else +        { +            stop(); +        } +        return; +    } + +    if (mFMODInternetStreamChannelp) +    { +        FMOD::Sound *sound = NULL; + +        if (mFMODInternetStreamChannelp->getCurrentSound(&sound) == FMOD_OK && sound) +        { +            FMOD_TAG tag; +            S32 tagcount, dirtytagcount; + +            if (sound->getNumTags(&tagcount, &dirtytagcount) == FMOD_OK && dirtytagcount) +            { +                for (S32 i = 0; i < tagcount; ++i) +                { +                    if (sound->getTag(NULL, i, &tag) != FMOD_OK) +                        continue; + +                    if (tag.type == FMOD_TAGTYPE_FMOD) +                    { +                        if (!strcmp(tag.name, "Sample Rate Change")) +                        { +                            LL_INFOS("FMOD") << "Stream forced changing sample rate to " << *((float *)tag.data) << LL_ENDL; +                            mFMODInternetStreamChannelp->setFrequency(*((float *)tag.data)); +                        } +                        continue; +                    } +                } +            } + +            if (starving) +            { +                bool paused = false; +                mFMODInternetStreamChannelp->getPaused(&paused); +                if (!paused) +                { +                    LL_INFOS("FMOD") << "Stream starvation detected! Pausing stream until buffer nearly full." << LL_ENDL; +                    LL_INFOS("FMOD") << "  (diskbusy=" << diskbusy << ")" << LL_ENDL; +                    LL_INFOS("FMOD") << "  (progress=" << progress << ")" << LL_ENDL; +                    mFMODInternetStreamChannelp->setPaused(true); +                } +            } +            else if (progress > 80) +            { +                mFMODInternetStreamChannelp->setPaused(false); +            } +        } +    } +} + +void LLStreamingAudio_FMODSTUDIO::stop() +{ +    if (mFMODInternetStreamChannelp) +    { +        mFMODInternetStreamChannelp->setPaused(true); +        mFMODInternetStreamChannelp->setPriority(0); +        mFMODInternetStreamChannelp = NULL; +    } + +    if (mCurrentInternetStreamp) +    { +        LL_INFOS("FMOD") << "Stopping internet stream: " << mCurrentInternetStreamp->getURL() << LL_ENDL; +        if (mCurrentInternetStreamp->stopStream()) +        { +            delete mCurrentInternetStreamp; +        } +        else +        { +            LL_WARNS("FMOD") << "Pushing stream to dead list: " << mCurrentInternetStreamp->getURL() << LL_ENDL; +            mDeadStreams.push_back(mCurrentInternetStreamp); +        } +        mCurrentInternetStreamp = NULL; +        //mURL.clear(); +    } +} + +void LLStreamingAudio_FMODSTUDIO::pause(int pauseopt) +{ +    if (pauseopt < 0) +    { +        pauseopt = mCurrentInternetStreamp ? 1 : 0; +    } + +    if (pauseopt) +    { +        if (mCurrentInternetStreamp) +        { +            LL_INFOS("FMOD") << "Pausing internet stream" << LL_ENDL; +            stop(); +        } +    } +    else +    { +        start(getURL()); +    } +} + + +// A stream is "playing" if it has been requested to start.  That +// doesn't necessarily mean audio is coming out of the speakers. +int LLStreamingAudio_FMODSTUDIO::isPlaying() +{ +    if (mCurrentInternetStreamp) +    { +        return 1; // Active and playing +    } +    else if (!mURL.empty()) +    { +        return 2; // "Paused" +    } +    else +    { +        return 0; +    } +} + + +F32 LLStreamingAudio_FMODSTUDIO::getGain() +{ +    return mGain; +} + + +std::string LLStreamingAudio_FMODSTUDIO::getURL() +{ +    return mURL; +} + + +void LLStreamingAudio_FMODSTUDIO::setGain(F32 vol) +{ +    mGain = vol; + +    if (mFMODInternetStreamChannelp) +    { +        vol = llclamp(vol * vol, 0.f, 1.f); //should vol be squared here? + +        mFMODInternetStreamChannelp->setVolume(vol); +    } +} + +/////////////////////////////////////////////////////// +// manager of possibly-multiple internet audio streams + +LLAudioStreamManagerFMODSTUDIO::LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, const std::string& url) : +mSystem(system), +mStreamChannel(NULL), +mInternetStream(NULL), +mReady(false) +{ +    mInternetStreamURL = url; + +    FMOD_RESULT result = mSystem->createStream(url.c_str(), FMOD_2D | FMOD_NONBLOCKING | FMOD_IGNORETAGS, 0, &mInternetStream); + +    if (result != FMOD_OK) +    { +        LL_WARNS("FMOD") << "Couldn't open fmod stream, error " +            << FMOD_ErrorString(result) +            << LL_ENDL; +        mReady = false; +        return; +    } + +    mReady = true; +} + +FMOD::Channel *LLAudioStreamManagerFMODSTUDIO::startStream() +{ +    // We need a live and opened stream before we try and play it. +    if (!mInternetStream || getOpenState() != FMOD_OPENSTATE_READY) +    { +        LL_WARNS("FMOD") << "No internet stream to start playing!" << LL_ENDL; +        return NULL; +    } + +    if (mStreamChannel) +        return mStreamChannel;  //Already have a channel for this stream. + +    FMOD_RESULT result = mSystem->playSound(mInternetStream, NULL, true, &mStreamChannel); +    if (result != FMOD_OK) +    { +        LL_WARNS("FMOD") << FMOD_ErrorString(result) << LL_ENDL; +    } +    return mStreamChannel; +} + +bool LLAudioStreamManagerFMODSTUDIO::stopStream() +{ +    if (mInternetStream) +    { + + +        bool close = true; +        switch (getOpenState()) +        { +        case FMOD_OPENSTATE_CONNECTING: +            close = false; +            break; +        default: +            close = true; +        } + +        if (close) +        { +            mInternetStream->release(); +            mStreamChannel = NULL; +            mInternetStream = NULL; +            return true; +        } +        else +        { +            return false; +        } +    } +    else +    { +        return true; +    } +} + +FMOD_OPENSTATE LLAudioStreamManagerFMODSTUDIO::getOpenState(unsigned int* percentbuffered, bool* starving, bool* diskbusy) +{ +    FMOD_OPENSTATE state; +    FMOD_RESULT result = mInternetStream->getOpenState(&state, percentbuffered, starving, diskbusy); +    if (result != FMOD_OK) +    { +        LL_WARNS("FMOD") << FMOD_ErrorString(result) << LL_ENDL; +    } +    return state; +} + +void LLStreamingAudio_FMODSTUDIO::setBufferSizes(U32 streambuffertime, U32 decodebuffertime) +{ +    FMOD_RESULT result = mSystem->setStreamBufferSize(streambuffertime / 1000 * 128 * 128, FMOD_TIMEUNIT_RAWBYTES); +    if (result != FMOD_OK) +    { +        LL_WARNS("FMOD") << "setStreamBufferSize error: " << FMOD_ErrorString(result) << LL_ENDL; +        return; +    } +    FMOD_ADVANCEDSETTINGS settings; +    memset(&settings, 0, sizeof(settings)); +    settings.cbSize = sizeof(settings); +    settings.defaultDecodeBufferSize = decodebuffertime;//ms +    result = mSystem->setAdvancedSettings(&settings); +    if (result != FMOD_OK) +    { +        LL_WARNS("FMOD") << "setAdvancedSettings error: " << FMOD_ErrorString(result) << LL_ENDL; +    } +} diff --git a/indra/llaudio/llstreamingaudio_fmodstudio.h b/indra/llaudio/llstreamingaudio_fmodstudio.h new file mode 100644 index 0000000000..2127841809 --- /dev/null +++ b/indra/llaudio/llstreamingaudio_fmodstudio.h @@ -0,0 +1,76 @@ +/** + * @file streamingaudio_fmodstudio.h + * @brief Definition of LLStreamingAudio_FMODSTUDIO implementation + * + * $LicenseInfo:firstyear=2020&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2020, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA + * $/LicenseInfo$ + */ + +#ifndef LL_STREAMINGAUDIO_FMODSTUDIO_H +#define LL_STREAMINGAUDIO_FMODSTUDIO_H + +#include "stdtypes.h" // from llcommon + +#include "llstreamingaudio.h" +#include "lltimer.h" + +//Stubs +class LLAudioStreamManagerFMODSTUDIO; +namespace FMOD +{ +    class System; +    class Channel; +} + +//Interfaces +class LLStreamingAudio_FMODSTUDIO : public LLStreamingAudioInterface +{ +public: +    LLStreamingAudio_FMODSTUDIO(FMOD::System *system); +    /*virtual*/ ~LLStreamingAudio_FMODSTUDIO(); + +    /*virtual*/ void start(const std::string& url); +    /*virtual*/ void stop(); +    /*virtual*/ void pause(S32 pause); +    /*virtual*/ void update(); +    /*virtual*/ S32 isPlaying(); +    /*virtual*/ void setGain(F32 vol); +    /*virtual*/ F32 getGain(); +    /*virtual*/ std::string getURL(); + +    /*virtual*/ bool supportsAdjustableBufferSizes(){return true;} +    /*virtual*/ void setBufferSizes(U32 streambuffertime, U32 decodebuffertime); +private: +    void killDeadStreams(); + +    FMOD::System *mSystem; + +    LLAudioStreamManagerFMODSTUDIO *mCurrentInternetStreamp; +    FMOD::Channel *mFMODInternetStreamChannelp; +    std::list<LLAudioStreamManagerFMODSTUDIO *> mDeadStreams; + +    std::string mURL; +    F32 mGain; +    S32 mRetryCount; +}; + + +#endif // LL_STREAMINGAUDIO_FMODSTUDIO_H | 
