Fixed wind dsp and implemented a DSP that mimics removed FMOD::Channel::getWaveData func.

Fixed crash if fmod profiling was enabled.
Fixed crash due to failing to check if resulting utf string from stream metadata was zero-length before calling std::string::back.
This commit is contained in:
Shyotl
2014-11-25 16:32:04 -06:00
parent c2abbaedc8
commit b3d86e626b
5 changed files with 262 additions and 83 deletions

View File

@@ -33,17 +33,25 @@
#include "linden_common.h"
#include "llmath.h"
#include "llthread.h"
#include "fmod.hpp"
#include "fmod_errors.h"
#include "llstreamingaudio_fmodstudio.h"
inline bool Check_FMOD_Error(FMOD_RESULT result, const char *string)
{
if (result == FMOD_OK)
return false;
LL_WARNS("AudioImpl") << string << " Error: " << FMOD_ErrorString(result) << LL_ENDL;
return true;
}
class LLAudioStreamManagerFMODSTUDIO
{
public:
LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, const std::string& url);
LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, FMOD::ChannelGroup *group, const std::string& url);
FMOD::Channel* startStream();
bool stopStream(); // Returns true if the stream was successfully stopped.
bool ready();
@@ -55,12 +63,55 @@ protected:
FMOD::System* mSystem;
FMOD::Channel* mStreamChannel;
FMOD::Sound* mInternetStream;
FMOD::ChannelGroup* mChannelGroup;
bool mReady;
std::string mInternetStreamURL;
};
LLGlobalMutex gWaveDataMutex; //Just to be extra strict.
const U32 WAVE_BUFFER_SIZE = 1024;
U32 gWaveBufferMinSize = 0;
F32 gWaveDataBuffer[WAVE_BUFFER_SIZE] = { 0.f };
U32 gWaveDataBufferSize = 0;
FMOD_RESULT F_CALLBACK waveDataCallback(FMOD_DSP_STATE *dsp_state, float *inbuffer, float *outbuffer, unsigned int length, int inchannels, int *outchannels)
{
if (!length || !inchannels)
return FMOD_OK;
memcpy(outbuffer, inbuffer, length * inchannels * sizeof(float));
static std::vector<F32> local_buf;
if (local_buf.size() < length)
local_buf.resize(length, 0.f);
for (U32 i = 0; i < length; ++i)
{
F32 total = 0.f;
for (S32 j = 0; j < inchannels; ++j)
{
total += inbuffer[i*inchannels + j];
}
local_buf[i] = total / inchannels;
}
{
LLMutexLock lock(gWaveDataMutex);
for (U32 i = length; i > 0; --i)
{
if (++gWaveDataBufferSize > WAVE_BUFFER_SIZE)
{
if (gWaveBufferMinSize)
memcpy(gWaveDataBuffer + WAVE_BUFFER_SIZE - gWaveBufferMinSize, gWaveDataBuffer, gWaveBufferMinSize * sizeof(float));
gWaveDataBufferSize = 1 + gWaveBufferMinSize;
}
gWaveDataBuffer[WAVE_BUFFER_SIZE - gWaveDataBufferSize] = local_buf[i - 1];
}
}
return FMOD_OK;
}
//---------------------------------------------------------------------------
// Internet Streaming
@@ -72,11 +123,14 @@ LLStreamingAudio_FMODSTUDIO::LLStreamingAudio_FMODSTUDIO(FMOD::System *system) :
mGain(1.0f),
mMetaData(NULL)
{
FMOD_RESULT result;
// 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
mSystem->setStreamBufferSize(estimated_bitrate * buffer_seconds * 128/*bytes/kbit*/, FMOD_TIMEUNIT_RAWBYTES);
result = mSystem->setStreamBufferSize(estimated_bitrate * buffer_seconds * 128/*bytes/kbit*/, FMOD_TIMEUNIT_RAWBYTES);
Check_FMOD_Error(result, "FMOD::System::setStreamBufferSize");
// 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
@@ -85,11 +139,36 @@ LLStreamingAudio_FMODSTUDIO::LLStreamingAudio_FMODSTUDIO(FMOD::System *system) :
// Leave the net buffer properties at the default.
//FSOUND_Stream_Net_SetBufferProperties(20000, 40, 80);
result = system->createChannelGroup("stream", &mStreamGroup);
Check_FMOD_Error(result,"FMOD::System::createChannelGroup");
FMOD_DSP_DESCRIPTION dspdesc;
memset(&dspdesc, 0, sizeof(FMOD_DSP_DESCRIPTION)); //Zero out everything
dspdesc.pluginsdkversion = FMOD_PLUGIN_SDK_VERSION;
strncpy(dspdesc.name, "Waveform", sizeof(dspdesc.name));
dspdesc.numoutputbuffers = 1;
dspdesc.read = &waveDataCallback; //Assign callback.
result = system->createDSP(&dspdesc, &mStreamDSP);
Check_FMOD_Error(result, "FMOD::System::createDSPByType");
result = mStreamGroup->addDSP(FMOD_CHANNELCONTROL_DSP_TAIL, mStreamDSP);
Check_FMOD_Error(result, "FMOD::ChannelGroup::addDSP");
mStreamDSP->setActive(false);
}
LLStreamingAudio_FMODSTUDIO::~LLStreamingAudio_FMODSTUDIO()
{
if (mStreamGroup)
{
if (mStreamDSP)
mStreamGroup->removeDSP(mStreamDSP);
mStreamGroup->release();
}
if (mStreamDSP)
mStreamDSP->release();
// nothing interesting/safe to do.
}
@@ -110,7 +189,7 @@ void LLStreamingAudio_FMODSTUDIO::start(const std::string& url)
if(mDeadStreams.empty())
{
llinfos << "Starting internet stream: " << url << llendl;
mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem,url);
mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem, mStreamGroup, url);
mURL = url;
mMetaData = new LLSD;
}
@@ -127,6 +206,45 @@ void LLStreamingAudio_FMODSTUDIO::start(const std::string& url)
}
}
enum utf_endian_type_t
{
UTF16LE,
UTF16BE,
UTF16
};
std::string utf16input_to_utf8(char* input, U32 len, utf_endian_type_t type)
{
if (type == UTF16)
{
type = UTF16BE; //Default
if (len > 2)
{
//Parse and strip BOM.
if ((input[0] == 0xFE && input[1] == 0xFF) ||
(input[0] == 0xFF && input[1] == 0xFE))
{
input += 2;
len -= 2;
type = input[0] == 0xFE ? UTF16BE : UTF16LE;
}
}
}
llutf16string out_16((U16*)input, len / 2);
if (len % 2)
{
out_16.push_back((input)[len - 1] << 8);
}
if (type == UTF16BE)
{
for (llutf16string::iterator i = out_16.begin(); i < out_16.end(); ++i)
{
llutf16string::value_type v = *i;
*i = ((v & 0x00FF) << 8) | ((v & 0xFF00) >> 8);
}
}
return utf16str_to_utf8str(out_16);
}
void LLStreamingAudio_FMODSTUDIO::update()
{
@@ -157,7 +275,7 @@ void LLStreamingAudio_FMODSTUDIO::update()
{
llassert_always(mCurrentInternetStreamp == NULL);
llinfos << "Starting internet stream: " << mPendingURL << llendl;
mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem,mPendingURL);
mCurrentInternetStreamp = new LLAudioStreamManagerFMODSTUDIO(mSystem,mStreamGroup, mPendingURL);
mURL = mPendingURL;
mMetaData = new LLSD;
mPendingURL.clear();
@@ -184,6 +302,8 @@ void LLStreamingAudio_FMODSTUDIO::update()
{
// Reset volume to previously set volume
setGain(getGain());
if (mStreamDSP)
mStreamDSP->setActive(true);
mFMODInternetStreamChannelp->setPaused(false);
}
}
@@ -216,23 +336,27 @@ void LLStreamingAudio_FMODSTUDIO::update()
switch(tag.type) //Crappy tag translate table.
{
case(FMOD_TAGTYPE_ID3V2):
if(name == "TIT2") name = "TITLE";
if (!LLStringUtil::compareInsensitive(name, "TIT2")) name = "TITLE";
else if(name == "TPE1") name = "ARTIST";
break;
case(FMOD_TAGTYPE_ASF):
if(name == "Title") name = "TITLE";
else if(name == "WM/AlbumArtist") name = "ARTIST";
if (!LLStringUtil::compareInsensitive(name, "Title")) name = "TITLE";
else if (!LLStringUtil::compareInsensitive(name, "WM/AlbumArtist")) name = "ARTIST";
break;
case(FMOD_TAGTYPE_FMOD):
if (!strcmp(tag.name, "Sample Rate Change"))
if (!LLStringUtil::compareInsensitive(name, "Sample Rate Change"))
{
llinfos << "Stream forced changing sample rate to " << *((float *)tag.data) << llendl;
mFMODInternetStreamChannelp->setFrequency(*((float *)tag.data));
}
continue;
default:
if (!LLStringUtil::compareInsensitive(name, "TITLE") ||
!LLStringUtil::compareInsensitive(name, "ARTIST"))
LLStringUtil::toUpper(name);
break;
}
switch(tag.datatype)
{
case(FMOD_TAGDATATYPE_INT):
@@ -246,25 +370,40 @@ void LLStreamingAudio_FMODSTUDIO::update()
case(FMOD_TAGDATATYPE_STRING):
{
std::string out = rawstr_to_utf8(std::string((char*)tag.data,tag.datalen));
if (out.length() && out.back() == 0)
out.pop_back();
(*mMetaData)[name]=out;
llinfos << tag.name << ": " << out << llendl;
llinfos << tag.name << "(RAW): " << out << llendl;
}
break;
case(FMOD_TAGDATATYPE_STRING_UTF8) :
{
U8 offs = 0;
if (tag.datalen > 3 && ((char*)tag.data)[0] == 0xEF && ((char*)tag.data)[1] == 0xBB && ((char*)tag.data)[2] == 0xBF)
offs = 3;
std::string out((char*)tag.data + offs, tag.datalen - offs);
if (out.length() && out.back() == 0)
out.pop_back();
(*mMetaData)[name] = out;
llinfos << tag.name << "(UTF8): " << out << llendl;
}
break;
case(FMOD_TAGDATATYPE_STRING_UTF16):
{
std::string out((char*)tag.data,tag.datalen);
(*mMetaData)[std::string(tag.name)]=out;
llinfos << tag.name << ": " << out << llendl;
std::string out = utf16input_to_utf8((char*)tag.data, tag.datalen, UTF16);
if (out.length() && out.back() == 0)
out.pop_back();
(*mMetaData)[name] = out;
llinfos << tag.name << "(UTF16): " << out << llendl;
}
break;
case(FMOD_TAGDATATYPE_STRING_UTF16BE):
{
std::string out((char*)tag.data,tag.datalen);
U16* buf = (U16*)out.c_str();
for(U32 j = 0; j < out.size()/2; ++j)
(((buf[j] & 0xff)<<8) | ((buf[j] & 0xff00)>>8));
(*mMetaData)[std::string(tag.name)]=out;
llinfos << tag.name << ": " << out << llendl;
std::string out = utf16input_to_utf8((char*)tag.data, tag.datalen, UTF16BE);
if (out.length() && out.back() == 0)
out.pop_back();
(*mMetaData)[name] = out;
llinfos << tag.name << "(UTF16BE): " << out << llendl;
}
default:
break;
@@ -300,6 +439,15 @@ void LLStreamingAudio_FMODSTUDIO::stop()
delete mMetaData;
mMetaData = NULL;
}
if (mStreamDSP)
{
mSystem->lockDSP();
mStreamDSP->setActive(false);
gWaveDataBufferSize = 0;
mSystem->unlockDSP();
}
if (mFMODInternetStreamChannelp)
{
mFMODInternetStreamChannelp->setPaused(true);
@@ -390,38 +538,40 @@ void LLStreamingAudio_FMODSTUDIO::setGain(F32 vol)
/*virtual*/ bool LLStreamingAudio_FMODSTUDIO::getWaveData(float* arr, S32 count, S32 stride/*=1*/)
{
//if(!mFMODInternetStreamChannelp || !mCurrentInternetStreamp)
// return false;
if (count > (WAVE_BUFFER_SIZE / 2))
LL_ERRS("AudioImpl") << "Count=" << count << " exceeds WAVE_BUFFER_SIZE/2=" << WAVE_BUFFER_SIZE << LL_ENDL;
//bool muted=false;
//mFMODInternetStreamChannelp->getMute(&muted);
//if(muted)
// return false;
if(!mFMODInternetStreamChannelp || !mCurrentInternetStreamp)
return false;
//static std::vector<float> local_array(count); //Have to have an extra buffer to mix channels. Bleh.
//if(count > (S32)local_array.size()) //Expand the array if needed. Try to minimize allocation calls, so don't ever shrink.
// local_array.resize(count);
//if( mFMODInternetStreamChannelp->getWaveData(&local_array[0],count,0) == FMOD_OK &&
// mFMODInternetStreamChannelp->getWaveData(&arr[0],count,1) == FMOD_OK )
//{
// for(S32 i = count-1;i>=0;i-=stride)
// {
// arr[i] += local_array[i];
// arr[i] *= .5f;
// }
// return true;
//}
return false;
bool muted = false;
mFMODInternetStreamChannelp->getMute(&muted);
if(muted)
return false;
{
U32 buff_size;
{
LLMutexLock lock(gWaveDataMutex);
gWaveBufferMinSize = count;
buff_size = gWaveDataBufferSize;
if (!buff_size)
return false;
memcpy(arr, gWaveDataBuffer + WAVE_BUFFER_SIZE - buff_size, llmin(U32(count), buff_size) * sizeof(float));
}
if (buff_size < U32(count))
memset(arr + buff_size, 0, (count - buff_size) * sizeof(float));
}
return true;
}
///////////////////////////////////////////////////////
// manager of possibly-multiple internet audio streams
LLAudioStreamManagerFMODSTUDIO::LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, const std::string& url) :
LLAudioStreamManagerFMODSTUDIO::LLAudioStreamManagerFMODSTUDIO(FMOD::System *system, FMOD::ChannelGroup *group, const std::string& url) :
mSystem(system),
mStreamChannel(NULL),
mInternetStream(NULL),
mChannelGroup(group),
mReady(false)
{
mInternetStreamURL = url;
@@ -452,7 +602,7 @@ FMOD::Channel *LLAudioStreamManagerFMODSTUDIO::startStream()
if(mStreamChannel)
return mStreamChannel; //Already have a channel for this stream.
mSystem->playSound(mInternetStream, NULL, true, &mStreamChannel);
mSystem->playSound(mInternetStream, mChannelGroup, true, &mStreamChannel);
return mStreamChannel;
}