View of /linden_release/linden/indra/llaudio/audioengine_fmod.cpp
Parent Directory
|
Revision Log
Revision 57 -
(download)
(annotate)
Wed Nov 19 00:33:37 2008 UTC (4 years, 6 months ago) by mjm
File size: 29816 byte(s)
Wed Nov 19 00:33:37 2008 UTC (4 years, 6 months ago) by mjm
File size: 29816 byte(s)
Importing Linden release 1.21.6 for merging future releases.
/**
* @file audioengine_fmod.cpp
* @brief Implementation of LLAudioEngine class abstracting the audio
* support as a FMOD 3D implementation
*
* $LicenseInfo:firstyear=2002&license=viewergpl$
*
* Copyright (c) 2002-2008, Linden Research, Inc.
*
* Second Life Viewer Source Code
* The source code in this file ("Source Code") is provided by Linden Lab
* to you under the terms of the GNU General Public License, version 2.0
* ("GPL"), unless you have obtained a separate licensing agreement
* ("Other License"), formally executed by you and Linden Lab. Terms of
* the GPL can be found in doc/GPL-license.txt in this distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/gplv2
*
* There are special exceptions to the terms and conditions of the GPL as
* it is applied to this Source Code. View the full text of the exception
* in the file doc/FLOSS-exception.txt in this software distribution, or
* online at http://secondlifegrid.net/programs/open_source/licensing/flossexception
*
* By copying, modifying or distributing this software, you acknowledge
* that you have read and understood your obligations described above,
* and agree to abide by those obligations.
*
* ALL LINDEN LAB SOURCE CODE IS PROVIDED "AS IS." LINDEN LAB MAKES NO
* WARRANTIES, EXPRESS, IMPLIED OR OTHERWISE, REGARDING ITS ACCURACY,
* COMPLETENESS OR PERFORMANCE.
* $/LicenseInfo$
*/
#include "linden_common.h"
#include "audioengine_fmod.h"
#include "listener_fmod.h"
#include "llerror.h"
#include "llmath.h"
#include "llrand.h"
#include "fmod.h"
#include "fmod_errors.h"
#include "lldir.h"
#include "llapr.h"
#include "sound_ids.h"
void * F_CALLBACKAPI windCallback(void *originalbuffer, void *newbuffer, int length, void* userdata);
FSOUND_DSPUNIT *gWindDSP = NULL;
// These globals for the wind filter. Blech!
F64 gbuf0 = 0.0;
F64 gbuf1 = 0.0;
F64 gbuf2 = 0.0;
F64 gbuf3 = 0.0;
F64 gbuf4 = 0.0;
F64 gbuf5 = 0.0;
F64 gY0 = 0.0;
F64 gY1 = 0.0;
F32 gTargetGain = 0.f;
F32 gCurrentGain = 0.f;
F32 gTargetFreq = 100.f;
F32 gCurrentFreq = 100.f;
F32 gTargetPanGainR = 0.5f;
F32 gCurrentPanGainR = 0.5f;
// Safe strcpy
#if 0 //(unused) //LL_WINDOWS || LL_LINUX
static size_t strlcpy( char* dest, const char* src, size_t dst_size )
{
size_t source_len = 0;
size_t min_len = 0;
if( dst_size > 0 )
{
if( src )
{
source_len = strlen(src); /*Flawfinder: ignore*/
min_len = llmin( dst_size - 1, source_len );
memcpy(dest, src, min_len); /*Flawfinder: ignore*/
}
dest[min_len] = '\0';
}
return source_len;
}
#else
// apple ships with the non-standard strlcpy in /usr/include/string.h:
// size_t strlcpy(char *, const char *, size_t);
#endif
LLAudioEngine_FMOD::LLAudioEngine_FMOD()
{
mInited = FALSE;
mCurrentInternetStreamp = NULL;
mInternetStreamChannel = -1;
}
LLAudioEngine_FMOD::~LLAudioEngine_FMOD()
{
}
BOOL LLAudioEngine_FMOD::init(const S32 num_channels, void* userdata)
{
mFadeIn = -10000;
LLAudioEngine::init(num_channels, userdata);
// Reserve one extra channel for the http stream.
if (!FSOUND_SetMinHardwareChannels(num_channels + 1))
{
LL_WARNS("AppInit") << "FMOD::init[0](), error: " << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
}
LL_DEBUGS("AppInit") << "LLAudioEngine_FMOD::init() initializing FMOD" << LL_ENDL;
F32 version = FSOUND_GetVersion();
if (version < FMOD_VERSION)
{
LL_WARNS("AppInit") << "Error : You are using the wrong FMOD version (" << version
<< ")! You should be using FMOD " << FMOD_VERSION << LL_ENDL;
//return FALSE;
}
U32 fmod_flags = 0x0;
#if LL_WINDOWS
// Windows needs to know which window is frontmost.
// This must be called before FSOUND_Init() per the FMOD docs.
// This could be used to let FMOD handle muting when we lose focus,
// but we don't actually want to do that because we want to distinguish
// between minimized and not-focused states.
if (!FSOUND_SetHWND(userdata))
{
LL_WARNS("AppInit") << "Error setting FMOD window: "
<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
return FALSE;
}
// Play audio when we don't have focus.
// (For example, IM client on top of us.)
// This means we also try to play audio when minimized,
// so we manually handle muting in that case. JC
fmod_flags |= FSOUND_INIT_GLOBALFOCUS;
#endif
#if LL_LINUX
// initialize the FMOD engine
// This is a hack to use only FMOD's basic FPU mixer
// when the LL_VALGRIND environmental variable is set,
// otherwise valgrind will fall over on FMOD's MMX detection
if (getenv("LL_VALGRIND")) /*Flawfinder: ignore*/
{
LL_INFOS("AppInit") << "Pacifying valgrind in FMOD init." << LL_ENDL;
FSOUND_SetMixer(FSOUND_MIXER_QUALITY_FPU);
}
// If we don't set an output method, Linux FMOD always
// decides on OSS and fails otherwise. So we'll manually
// try ESD, then OSS, then ALSA.
// Why this order? See SL-13250, but in short, OSS emulated
// on top of ALSA is ironically more reliable than raw ALSA.
// Ack, and ESD has more reliable failure modes - but has worse
// latency - than all of them, so wins for now.
BOOL audio_ok = FALSE;
if (!audio_ok)
if (NULL == getenv("LL_BAD_ESD")) /*Flawfinder: ignore*/
{
LL_DEBUGS("AppInit") << "Trying ESD audio output..." << LL_ENDL;
if(FSOUND_SetOutput(FSOUND_OUTPUT_ESD) &&
FSOUND_Init(44100, num_channels, fmod_flags))
{
LL_DEBUGS("AppInit") << "ESD audio output initialized OKAY"
<< LL_ENDL;
audio_ok = TRUE;
} else {
LL_WARNS("AppInit") << "ESD audio output FAILED to initialize: "
<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
}
} else {
LL_DEBUGS("AppInit") << "ESD audio output SKIPPED" << LL_ENDL;
}
if (!audio_ok)
if (NULL == getenv("LL_BAD_OSS")) /*Flawfinder: ignore*/
{
LL_DEBUGS("AppInit") << "Trying OSS audio output..." << LL_ENDL;
if(FSOUND_SetOutput(FSOUND_OUTPUT_OSS) &&
FSOUND_Init(44100, num_channels, fmod_flags))
{
LL_DEBUGS("AppInit") << "OSS audio output initialized OKAY" << LL_ENDL;
audio_ok = TRUE;
} else {
LL_WARNS("AppInit") << "OSS audio output FAILED to initialize: "
<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
}
} else {
LL_DEBUGS("AppInit") << "OSS audio output SKIPPED" << LL_ENDL;
}
if (!audio_ok)
if (NULL == getenv("LL_BAD_ALSA")) /*Flawfinder: ignore*/
{
LL_DEBUGS("AppInit") << "Trying ALSA audio output..." << LL_ENDL;
if(FSOUND_SetOutput(FSOUND_OUTPUT_ALSA) &&
FSOUND_Init(44100, num_channels, fmod_flags))
{
LL_DEBUGS("AppInit") << "ALSA audio output initialized OKAY" << LL_ENDL;
audio_ok = TRUE;
} else {
LL_WARNS("AppInit") << "ALSA audio output FAILED to initialize: "
<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
}
} else {
LL_DEBUGS("AppInit") << "OSS audio output SKIPPED" << LL_ENDL;
}
if (!audio_ok)
{
LL_WARNS("AppInit") << "Overall audio init failure." << LL_ENDL;
return FALSE;
}
// On Linux, FMOD causes a SIGPIPE for some netstream error
// conditions (an FMOD bug); ignore SIGPIPE so it doesn't crash us.
// NOW FIXED in FMOD 3.x since 2006-10-01.
//signal(SIGPIPE, SIG_IGN);
// We're interested in logging which output method we
// ended up with, for QA purposes.
switch (FSOUND_GetOutput())
{
case FSOUND_OUTPUT_NOSOUND: LL_DEBUGS("AppInit") << "Audio output: NoSound" << LL_ENDL; break;
case FSOUND_OUTPUT_OSS: LL_DEBUGS("AppInit") << "Audio output: OSS" << LL_ENDL; break;
case FSOUND_OUTPUT_ESD: LL_DEBUGS("AppInit") << "Audio output: ESD" << LL_ENDL; break;
case FSOUND_OUTPUT_ALSA: LL_DEBUGS("AppInit") << "Audio output: ALSA" << LL_ENDL; break;
default: LL_INFOS("AppInit") << "Audio output: Unknown!" << LL_ENDL; break;
};
#else // LL_LINUX
// initialize the FMOD engine
if (!FSOUND_Init(44100, num_channels, fmod_flags))
{
LL_WARNS("AppInit") << "Error initializing FMOD: "
<< FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
return FALSE;
}
#endif
initInternetStream();
LL_DEBUGS("AppInit") << "LLAudioEngine_FMOD::init() FMOD initialized correctly" << LL_ENDL;
mInited = TRUE;
return TRUE;
}
void LLAudioEngine_FMOD::idle(F32 max_decode_time)
{
LLAudioEngine::idle(max_decode_time);
updateInternetStream();
}
void LLAudioEngine_FMOD::allocateListener(void)
{
mListenerp = (LLListener *) new LLListener_FMOD();
if (!mListenerp)
{
llwarns << "Listener creation failed" << llendl;
}
}
void LLAudioEngine_FMOD::shutdown()
{
if (gWindDSP)
{
FSOUND_DSP_SetActive(gWindDSP,FALSE);
FSOUND_DSP_Free(gWindDSP);
}
stopInternetStream();
LLAudioEngine::shutdown();
llinfos << "LLAudioEngine_FMOD::shutdown() closing FMOD" << llendl;
FSOUND_Close();
llinfos << "LLAudioEngine_FMOD::shutdown() done closing FMOD" << llendl;
delete mListenerp;
mListenerp = NULL;
}
LLAudioBuffer *LLAudioEngine_FMOD::createBuffer()
{
return new LLAudioBufferFMOD();
}
LLAudioChannel *LLAudioEngine_FMOD::createChannel()
{
return new LLAudioChannelFMOD();
}
void LLAudioEngine_FMOD::initWind()
{
if (!gWindDSP)
{
gWindDSP = FSOUND_DSP_Create(&windCallback, FSOUND_DSP_DEFAULTPRIORITY_CLEARUNIT + 20, NULL);
}
if (gWindDSP)
{
FSOUND_DSP_SetActive(gWindDSP, TRUE);
}
mNextWindUpdate = 0.0;
}
void LLAudioEngine_FMOD::cleanupWind()
{
if (gWindDSP)
{
FSOUND_DSP_SetActive(gWindDSP, FALSE);
FSOUND_DSP_Free(gWindDSP);
gWindDSP = NULL;
}
}
//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::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));
gTargetFreq = (F32)center_freq;
gTargetGain = (F32)mapWindVecToGain(wind_vec) * mMaxWindGain;
gTargetPanGainR = (F32)mapWindVecToPan(wind_vec);
}
}
/*
//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::setSourceMinDistance(U16 source_num, F64 distance)
{
if (!mInited)
{
return;
}
if (mBuffer[source_num])
{
mMinDistance[source_num] = (F32) distance;
if (!FSOUND_Sample_SetMinMaxDistance(mBuffer[source_num],mMinDistance[source_num], mMaxDistance[source_num]))
{
llwarns << "FMOD::setSourceMinDistance(" << source_num << "), error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
}
}
}
//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::setSourceMaxDistance(U16 source_num, F64 distance)
{
if (!mInited)
{
return;
}
if (mBuffer[source_num])
{
mMaxDistance[source_num] = (F32) distance;
if (!FSOUND_Sample_SetMinMaxDistance(mBuffer[source_num],mMinDistance[source_num], mMaxDistance[source_num]))
{
llwarns << "FMOD::setSourceMaxDistance(" << source_num << "), error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
}
}
}
//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::get3DParams(S32 source_num, S32 *volume, S32 *freq, S32 *inside, S32 *outside, LLVector3 *orient, S32 *out_volume, F32 *min_dist, F32 *max_dist)
{
*volume = 0;
*freq = 0;
*inside = 0;
*outside = 0;
*orient = LLVector3::zero;
*out_volume = 0;
*min_dist = 0.f;
*max_dist = 0.f;
}
*/
//-----------------------------------------------------------------------
void LLAudioEngine_FMOD::setInternalGain(F32 gain)
{
if (!mInited)
{
return;
}
gain = llclamp( gain, 0.0f, 1.0f );
FSOUND_SetSFXMasterVolume( llround( 255.0f * gain ) );
if ( mInternetStreamChannel != -1 )
{
F32 clamp_internet_stream_gain = llclamp( mInternetStreamGain, 0.0f, 1.0f );
FSOUND_SetVolumeAbsolute( mInternetStreamChannel, llround( 255.0f * clamp_internet_stream_gain ) );
}
}
//
// LLAudioChannelFMOD implementation
//
LLAudioChannelFMOD::LLAudioChannelFMOD() : LLAudioChannel(), mChannelID(0), mLastSamplePos(0)
{
}
LLAudioChannelFMOD::~LLAudioChannelFMOD()
{
cleanup();
}
BOOL LLAudioChannelFMOD::updateBuffer()
{
if (LLAudioChannel::updateBuffer())
{
// Base class update returned TRUE, which means that we need to actually
// set up the channel for a different buffer.
LLAudioBufferFMOD *bufferp = (LLAudioBufferFMOD *)mCurrentSourcep->getCurrentBuffer();
// Grab the FMOD sample associated with the buffer
FSOUND_SAMPLE *samplep = bufferp->getSample();
if (!samplep)
{
// This is bad, there should ALWAYS be a sample associated with a legit
// buffer.
llerrs << "No FMOD sample!" << llendl;
return FALSE;
}
// Actually play the sound. Start it off paused so we can do all the necessary
// setup.
mChannelID = FSOUND_PlaySoundEx(FSOUND_FREE, samplep, FSOUND_DSP_GetSFXUnit(), TRUE);
//llinfos << "Setting up channel " << std::hex << mChannelID << std::dec << llendl;
}
// If we have a source for the channel, we need to update its gain.
if (mCurrentSourcep)
{
// SJB: warnings can spam and hurt framerate, disabling
if (!FSOUND_SetVolume(mChannelID, llround(mCurrentSourcep->getGain() * 255.0f)))
{
// llwarns << "LLAudioChannelFMOD::updateBuffer error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
}
if (!FSOUND_SetLoopMode(mChannelID, mCurrentSourcep->isLoop() ? FSOUND_LOOP_NORMAL : FSOUND_LOOP_OFF))
{
// llwarns << "Channel " << mChannelID << "Source ID: " << mCurrentSourcep->getID()
// << " at " << mCurrentSourcep->getPositionGlobal() << llendl;
// llwarns << "LLAudioChannelFMOD::updateBuffer error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
}
}
return TRUE;
}
void LLAudioChannelFMOD::update3DPosition()
{
if (!mChannelID)
{
// We're not actually a live channel (i.e., we're not playing back anything)
return;
}
LLAudioBufferFMOD *bufferp = (LLAudioBufferFMOD *)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->isAmbient())
{
// Ambient sound, don't need to do any positional updates.
bufferp->set3DMode(FALSE);
}
else
{
// Localized sound. Update the position and velocity of the sound.
bufferp->set3DMode(TRUE);
LLVector3 float_pos;
float_pos.setVec(mCurrentSourcep->getPositionGlobal());
if (!FSOUND_3D_SetAttributes(mChannelID, float_pos.mV, mCurrentSourcep->getVelocity().mV))
{
LL_DEBUGS("FMOD") << "LLAudioChannelFMOD::update3DPosition error: " << FMOD_ErrorString(FSOUND_GetError()) << LL_ENDL;
}
}
}
void LLAudioChannelFMOD::updateLoop()
{
if (!mChannelID)
{
// May want to clear up the loop/sample counters.
return;
}
//
// Hack: We keep track of whether we looped or not by seeing when the sign of the last sample
// flips. This is pretty crappy.
//
U32 cur_pos = FSOUND_GetCurrentPosition(mChannelID);
if (cur_pos < (U32)mLastSamplePos)
{
mLoopedThisFrame = TRUE;
}
mLastSamplePos = cur_pos;
}
void LLAudioChannelFMOD::cleanup()
{
if (!mChannelID)
{
//llinfos << "Aborting cleanup with no channelID." << llendl;
return;
}
//llinfos << "Cleaning up channel: " << mChannelID << llendl;
if (!FSOUND_StopSound(mChannelID))
{
LL_DEBUGS("FMOD") << "LLAudioChannelFMOD::cleanup error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
}
mCurrentBufferp = NULL;
mChannelID = 0;
}
void LLAudioChannelFMOD::play()
{
if (!mChannelID)
{
llwarns << "Playing without a channelID, aborting" << llendl;
return;
}
if (!FSOUND_SetPaused(mChannelID, FALSE))
{
llwarns << "LLAudioChannelFMOD::play error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
}
getSource()->setPlayedOnce(TRUE);
}
void LLAudioChannelFMOD::playSynced(LLAudioChannel *channelp)
{
LLAudioChannelFMOD *fmod_channelp = (LLAudioChannelFMOD*)channelp;
if (!(fmod_channelp->mChannelID && mChannelID))
{
// Don't have channels allocated to both the master and the slave
return;
}
U32 position = FSOUND_GetCurrentPosition(fmod_channelp->mChannelID) % mCurrentBufferp->getLength();
// Try to match the position of our sync master
if (!FSOUND_SetCurrentPosition(mChannelID, position))
{
llwarns << "LLAudioChannelFMOD::playSynced unable to set current position" << llendl;
}
// Start us playing
play();
}
BOOL LLAudioChannelFMOD::isPlaying()
{
if (!mChannelID)
{
return FALSE;
}
return FSOUND_IsPlaying(mChannelID) && (!FSOUND_GetPaused(mChannelID));
}
//
// LLAudioBufferFMOD implementation
//
LLAudioBufferFMOD::LLAudioBufferFMOD()
{
mSamplep = NULL;
}
LLAudioBufferFMOD::~LLAudioBufferFMOD()
{
if (mSamplep)
{
// Clean up the associated FMOD sample if it exists.
FSOUND_Sample_Free(mSamplep);
mSamplep = NULL;
}
}
BOOL LLAudioBufferFMOD::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;
}
S32 file_size = 0;
apr_file_t* apr_file = ll_apr_file_open(filename, LL_APR_RPB, &file_size);
if (!apr_file)
{
// File not found, abort.
return FALSE;
}
apr_file_close(apr_file);
if (mSamplep)
{
// If there's already something loaded in this buffer, clean it up.
FSOUND_Sample_Free(mSamplep);
mSamplep = NULL;
}
// Load up the wav file into an fmod sample
#if LL_WINDOWS
// MikeS. - Loading the sound file manually and then handing it over to FMOD,
// since FMOD uses posix IO internally,
// which doesn't work with unicode file paths.
LLFILE* sound_file = LLFile::fopen(filename,"rb"); /* Flawfinder: ignore */
if (sound_file)
{
fseek(sound_file,0,SEEK_END);
U32 file_length = ftell(sound_file); //Find the length of the file by seeking to the end and getting the offset
size_t read_count;
fseek(sound_file,0,SEEK_SET); //Seek back to the beginning
char* buffer = new char[file_length];
llassert(buffer);
read_count = fread((void*)buffer,file_length,1,sound_file);//Load it..
if(ferror(sound_file)==0 && (read_count == 1)){//No read error, and we got 1 chunk of our size...
unsigned int mode_flags = FSOUND_LOOP_NORMAL | FSOUND_LOADMEMORY;
//FSOUND_16BITS | FSOUND_MONO | FSOUND_LOADMEMORY | FSOUND_LOOP_NORMAL;
mSamplep = FSOUND_Sample_Load(FSOUND_UNMANAGED, buffer, mode_flags , 0, file_length);
}
delete[] buffer;
fclose(sound_file);
}
#else
mSamplep = FSOUND_Sample_Load(FSOUND_UNMANAGED, filename.c_str(), FSOUND_LOOP_NORMAL, 0, 0);
#endif
if (!mSamplep)
{
// We failed to load the file for some reason.
llwarns << "Could not load data '" << filename << "': "
<< FMOD_ErrorString(FSOUND_GetError()) << llendl;
//
// 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 LLAudioBufferFMOD::getLength()
{
if (!mSamplep)
{
return 0;
}
return FSOUND_Sample_GetLength(mSamplep);
}
void LLAudioBufferFMOD::set3DMode(BOOL use3d)
{
U16 current_mode = FSOUND_Sample_GetMode(mSamplep);
if (use3d)
{
if (!FSOUND_Sample_SetMode(mSamplep, (current_mode & (~FSOUND_2D))))
{
llwarns << "LLAudioBufferFMOD::set3DMode error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
}
}
else
{
if (!FSOUND_Sample_SetMode(mSamplep, current_mode | FSOUND_2D))
{
llwarns << "LLAudioBufferFMOD::set3DMode error: " << FMOD_ErrorString(FSOUND_GetError()) << llendl;
}
}
}
//---------------------------------------------------------------------------
// Internet Streaming
//---------------------------------------------------------------------------
void LLAudioEngine_FMOD::initInternetStream()
{
// Number of milliseconds of audio to buffer for the audio card.
// Must be larger than the usual Second Life frame stutter time.
FSOUND_Stream_SetBufferSize(200);
// 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);
mInternetStreamURL.clear();
}
void LLAudioEngine_FMOD::startInternetStream(const std::string& url)
{
if (!mInited)
{
llwarns << "startInternetStream before audio initialized" << llendl;
return;
}
// "stop" stream but don't clear url, etc. in calse url == mInternetStreamURL
stopInternetStream();
if (!url.empty())
{
llinfos << "Starting internet stream: " << url << llendl;
mCurrentInternetStreamp = new LLAudioStreamFMOD(url);
mInternetStreamURL = url;
}
else
{
llinfos << "Set internet stream to null" << llendl;
mInternetStreamURL.clear();
}
}
signed char F_CALLBACKAPI LLAudioEngine_FMOD::callbackMetaData(char *name, char *value, void *userdata)
{
/*
LLAudioEngine_FMOD* self = (LLAudioEngine_FMOD*)userdata;
if (!strcmp("ARTIST", name))
{
strlcpy(self->mInternetStreamArtist, value, 256);
self->mInternetStreamNewMetaData = TRUE;
return TRUE;
}
if (!strcmp("TITLE", name))
{
strlcpy(self->mInternetStreamTitle, value, 256);
self->mInternetStreamNewMetaData = TRUE;
return TRUE;
}
*/
return TRUE;
}
void LLAudioEngine_FMOD::updateInternetStream()
{
// Kill dead internet streams, if possible
std::list<LLAudioStreamFMOD *>::iterator iter;
for (iter = mDeadStreams.begin(); iter != mDeadStreams.end();)
{
LLAudioStreamFMOD *streamp = *iter;
if (streamp->stopStream())
{
llinfos << "Closed dead stream" << llendl;
delete streamp;
mDeadStreams.erase(iter++);
}
else
{
iter++;
}
}
// Don't do anything if there are no streams playing
if (!mCurrentInternetStreamp)
{
return;
}
int open_state = mCurrentInternetStreamp->getOpenState();
if (!open_state)
{
// Stream is live
// start the stream if it's ready
if (mInternetStreamChannel < 0)
{
mInternetStreamChannel = mCurrentInternetStreamp->startStream();
if (mInternetStreamChannel != -1)
{
// Reset volume to previously set volume
setInternetStreamGain(mInternetStreamGain);
FSOUND_SetPaused(mInternetStreamChannel, FALSE);
//FSOUND_Stream_Net_SetMetadataCallback(mInternetStream, callbackMetaData, this);
}
}
}
switch(open_state)
{
default:
case 0:
// success
break;
case -1:
// stream handle is invalid
llwarns << "InternetStream - invalid handle" << llendl;
stopInternetStream();
return;
case -2:
// opening
//strlcpy(mInternetStreamArtist, "Opening", 256);
break;
case -3:
// failed to open, file not found, perhaps
llwarns << "InternetSteam - failed to open" << llendl;
stopInternetStream();
return;
case -4:
// connecting
//strlcpy(mInternetStreamArtist, "Connecting", 256);
break;
case -5:
// buffering
//strlcpy(mInternetStreamArtist, "Buffering", 256);
break;
}
}
void LLAudioEngine_FMOD::stopInternetStream()
{
if (mInternetStreamChannel != -1)
{
FSOUND_SetPaused(mInternetStreamChannel, TRUE);
FSOUND_SetPriority(mInternetStreamChannel, 0);
mInternetStreamChannel = -1;
}
if (mCurrentInternetStreamp)
{
llinfos << "Stopping internet stream: " << mCurrentInternetStreamp->getURL() << llendl;
if (mCurrentInternetStreamp->stopStream())
{
delete mCurrentInternetStreamp;
}
else
{
llwarns << "Pushing stream to dead list: " << mCurrentInternetStreamp->getURL() << llendl;
mDeadStreams.push_back(mCurrentInternetStreamp);
}
mCurrentInternetStreamp = NULL;
//mInternetStreamURL.clear();
}
}
void LLAudioEngine_FMOD::pauseInternetStream(int pause)
{
if (pause < 0)
{
pause = mCurrentInternetStreamp ? 1 : 0;
}
if (pause)
{
if (mCurrentInternetStreamp)
{
stopInternetStream();
}
}
else
{
startInternetStream(mInternetStreamURL);
}
}
// A stream is "playing" if it has been requested to start. That
// doesn't necessarily mean audio is coming out of the speakers.
int LLAudioEngine_FMOD::isInternetStreamPlaying()
{
if (mCurrentInternetStreamp)
{
return 1; // Active and playing
}
else if (!mInternetStreamURL.empty())
{
return 2; // "Paused"
}
else
{
return 0;
}
}
void LLAudioEngine_FMOD::getInternetStreamInfo(char* artist_out, char* title_out)
{
//strlcpy(artist_out, mInternetStreamArtist, 256);
//strlcpy(title_out, mInternetStreamTitle, 256);
}
void LLAudioEngine_FMOD::setInternetStreamGain(F32 vol)
{
LLAudioEngine::setInternetStreamGain(vol);
if (mInternetStreamChannel != -1)
{
vol = llclamp(vol, 0.f, 1.f);
int vol_int = llround(vol * 255.f);
FSOUND_SetVolumeAbsolute(mInternetStreamChannel, vol_int);
}
}
const std::string& LLAudioEngine_FMOD::getInternetStreamURL()
{
return mInternetStreamURL;
}
LLAudioStreamFMOD::LLAudioStreamFMOD(const std::string& url) :
mInternetStream(NULL),
mReady(FALSE)
{
mInternetStreamURL = url;
mInternetStream = FSOUND_Stream_Open(url.c_str(), FSOUND_NORMAL | FSOUND_NONBLOCKING, 0, 0);
if (!mInternetStream)
{
llwarns << "Couldn't open fmod stream, error "
<< FMOD_ErrorString(FSOUND_GetError())
<< llendl;
mReady = FALSE;
return;
}
mReady = TRUE;
}
int LLAudioStreamFMOD::startStream()
{
// We need a live and opened stream before we try and play it.
if (!mInternetStream || getOpenState())
{
llwarns << "No internet stream to start playing!" << llendl;
return -1;
}
// Make sure the stream is set to 2D mode.
FSOUND_Stream_SetMode(mInternetStream, FSOUND_2D);
return FSOUND_Stream_PlayEx(FSOUND_FREE, mInternetStream, NULL, TRUE);
}
BOOL LLAudioStreamFMOD::stopStream()
{
if (mInternetStream)
{
int read_percent = 0;
int status = 0;
int bitrate = 0;
unsigned int flags = 0x0;
FSOUND_Stream_Net_GetStatus(mInternetStream, &status, &read_percent, &bitrate, &flags);
BOOL close = TRUE;
switch (status)
{
case FSOUND_STREAM_NET_CONNECTING:
close = FALSE;
break;
case FSOUND_STREAM_NET_NOTCONNECTED:
case FSOUND_STREAM_NET_BUFFERING:
case FSOUND_STREAM_NET_READY:
case FSOUND_STREAM_NET_ERROR:
default:
close = TRUE;
}
if (close)
{
FSOUND_Stream_Close(mInternetStream);
mInternetStream = NULL;
return TRUE;
}
else
{
return FALSE;
}
}
else
{
return TRUE;
}
}
int LLAudioStreamFMOD::getOpenState()
{
int open_state = FSOUND_Stream_GetOpenState(mInternetStream);
return open_state;
}
/* This determines the format of the mixbuffer being passed in. change if you want to support int32 or float32 */
#if LL_DARWIN
#define MIXBUFFERFORMAT S32
#else
#define MIXBUFFERFORMAT S16
#endif
inline MIXBUFFERFORMAT clipSample(MIXBUFFERFORMAT sample, MIXBUFFERFORMAT min, MIXBUFFERFORMAT max)
{
if (sample > max)
sample = max;
else if (sample < min)
sample = min;
return sample;
}
void * F_CALLBACKAPI windCallback(void *originalbuffer, void *newbuffer, int length, void*)
{
// originalbuffer = fsounds original mixbuffer.
// newbuffer = the buffer passed from the previous DSP unit.
// length = length in samples at this mix time.
// param = user parameter passed through in FSOUND_DSP_Create.
//
// modify the buffer in some fashion
U8 *cursamplep = (U8*)newbuffer;
U8 wordsize = 2;
#if LL_DARWIN
wordsize = sizeof(MIXBUFFERFORMAT);
#else
int mixertype = FSOUND_GetMixer();
if (mixertype == FSOUND_MIXER_BLENDMODE || mixertype == FSOUND_MIXER_QUALITY_FPU)
{
wordsize = 4;
}
#endif
double bandwidth = 50;
double inputSamplingRate = 44100;
double a0,b1,b2;
// calculate resonant filter coeffs
b2 = exp(-(F_TWO_PI) * (bandwidth / inputSamplingRate));
while (length--)
{
gCurrentFreq = (float)((0.999 * gCurrentFreq) + (0.001 * gTargetFreq));
gCurrentGain = (float)((0.999 * gCurrentGain) + (0.001 * gTargetGain));
gCurrentPanGainR = (float)((0.999 * gCurrentPanGainR) + (0.001 * gTargetPanGainR));
b1 = (-4.0 * b2) / (1.0 + b2) * cos(F_TWO_PI * (gCurrentFreq / inputSamplingRate));
a0 = (1.0 - b2) * sqrt(1.0 - (b1 * b1) / (4.0 * b2));
double nextSample;
// start with white noise
nextSample = ll_frand(2.0f) - 1.0f;
#if 1 // LLAE_WIND_PINK apply pinking filter
gbuf0 = 0.997f * gbuf0 + 0.0126502f * nextSample;
gbuf1 = 0.985f * gbuf1 + 0.0139083f * nextSample;
gbuf2 = 0.950f * gbuf2 + 0.0205439f * nextSample;
gbuf3 = 0.850f * gbuf3 + 0.0387225f * nextSample;
gbuf4 = 0.620f * gbuf4 + 0.0465932f * nextSample;
gbuf5 = 0.250f * gbuf5 + 0.1093477f * nextSample;
nextSample = gbuf0 + gbuf1 + gbuf2 + gbuf3 + gbuf4 + gbuf5;
#endif
#if 1 //LLAE_WIND_RESONANT // do a resonant filter on the noise
nextSample = (double)( a0 * nextSample - b1 * gY0 - b2 * gY1 );
gY1 = gY0;
gY0 = nextSample;
#endif
nextSample *= gCurrentGain;
MIXBUFFERFORMAT sample;
sample = llfloor(((F32)nextSample*32768.f*(1.0f - gCurrentPanGainR))+0.5f);
*(MIXBUFFERFORMAT*)cursamplep = clipSample((*(MIXBUFFERFORMAT*)cursamplep) + sample, -32768, 32767);
cursamplep += wordsize;
sample = llfloor(((F32)nextSample*32768.f*gCurrentPanGainR)+0.5f);
*(MIXBUFFERFORMAT*)cursamplep = clipSample((*(MIXBUFFERFORMAT*)cursamplep) + sample, -32768, 32767);
cursamplep += wordsize;
}
return newbuffer;
}
| ViewVC Help | |
| Powered by ViewVC 1.0.0 |

