phoenix-firestorm/indra/llaudio/llaudioengine_sdl2.cpp

490 lines
9.7 KiB
C++

#include "llaudioengine_sdl2.h"
#include "llstreamingaudio.h"
#include "lllistener.h"
#include "SDL2/SDL_mixer.h"
#include "curl/curl.h"
namespace
{
std::string getMixerError()
{
auto pError = Mix_GetError();
if(!pError)
return "Mix_Error: returned nullptr";
return std::string{ "Mix_error: " } + pError;
}
}
struct RWData
{
std::string mURL;
CURLM *mMulti{ nullptr };
CURL *mEasy{ nullptr };
};
Sint64 SDLCALL stream_size (struct SDL_RWops * context)
{
return -1;
}
Sint64(SDLCALL stream_seek) (struct SDL_RWops * context, Sint64 offset,
int whence)
{
if( !context )
return -1;
RWData *pData = reinterpret_cast<RWData*>(context->hidden.unknown.data1);
return -1;
}
size_t SDLCALL stream_read (struct SDL_RWops * context, void *ptr, size_t size, size_t maxnum)
{
if( !context )
return 0;
RWData *pData = reinterpret_cast<RWData*>(context->hidden.unknown.data1);
return 0;
}
size_t SDLCALL stream_write (struct SDL_RWops * context, const void *ptr,
size_t size, size_t num)
{
if( !context )
return 0;
RWData *pData = reinterpret_cast<RWData*>(context->hidden.unknown.data1);
return 0;
}
int SDLCALL stream_close (struct SDL_RWops * context)
{
if( !context )
return 0;
RWData *pData = reinterpret_cast<RWData*>(context->hidden.unknown.data1);
// Free curl multi and easy
return 0;
}
class LLListenerSDL2: public LLListener
{
LLAudioEngineSDL2 *mEngine;
public:
LLListenerSDL2(LLAudioEngineSDL2 *aEngine )
: mEngine( aEngine )
{
}
};
class LLStreamingAudioSDL2: public LLStreamingAudioInterface
{
std::string mURL;
F32 mVolume { 1.0 };
Mix_Music *mMusic{nullptr};
SDL_RWops *mReader{ nullptr };
RWData *mData;
public:
void start(const std::string& url) override
{
mURL = url;
//mMusic = Mix_LoadMUS( url.c_str() );
mReader = SDL_AllocRW();
mData = new RWData{};
mReader->hidden.unknown.data1 = mData;
mReader->size = stream_size;
mReader->seek = stream_seek;
mReader->read = stream_read;
mReader->write = stream_write;
mReader->close = stream_close;
mMusic = Mix_LoadMUS_RW( mReader, 0 );
if( mMusic )
{
auto x = Mix_PlayMusic( mMusic, 0 );
LL_WARNS() << "SDL2: " << getMixerError() << " " << x << LL_ENDL;
}
else
{
LL_WARNS() << "SDL2: MixLoadMUS failed: " << getMixerError() << LL_ENDL;
}
}
void stop() override
{
}
void pause(int pause) override
{
}
void update() override
{
}
int isPlaying() override
{
return mMusic != nullptr;
}
void setGain(F32 vol) override
{
mVolume = vol;
}
F32 getGain() override
{
return mVolume;
}
std::string getURL() override
{
return mURL;
}
bool supportsAdjustableBufferSizes() override
{
return false;
}
void setBufferSizes(U32 streambuffertime, U32 decodebuffertime) override
{
}
};
class LLAudioBufferSDL2: public LLAudioBuffer
{
public:
Mix_Chunk *mChunk;
~LLAudioBufferSDL2()
{
if( mChunk )
Mix_FreeChunk(mChunk);
}
bool loadWAV(const std::string& filename)
{
mChunk = Mix_LoadWAV( filename.c_str() );
return mChunk != nullptr;
}
U32 getLength()
{
return 0; // Not needed here
}
};
class LLAudioChannelSDL2: public LLAudioChannel
{
S32 mChannel;
LLAudioEngineSDL2 *mEngine;
bool mPlayback{false};
uint32_t caclulateVolume()
{
if( mChannel < 0 || !mCurrentSourcep || !mEngine)
return 0.0f;
F32 gain = mCurrentSourcep->getGain() * getSecondaryGain() * mEngine->getMasterGain();
llclamp(gain, 0.0f, 1.f );
gain *= 255.f;
return static_cast<uint32_t>(gain);
}
public:
LLAudioChannelSDL2( S32 nChannel, LLAudioEngineSDL2 *aEngine )
: mChannel( nChannel )
, mEngine( aEngine )
{
}
~LLAudioChannelSDL2()
{
mEngine->deleteChannel( mChannel );
}
void play() override
{
startPlayback();
}
void startPlayback()
{
if( mChannel < 0 )
return;
//if( mPlayback )
// return;
Mix_HaltChannel( mChannel );
LLAudioBufferSDL2* pBuffer = (LLAudioBufferSDL2*)mCurrentBufferp;
if( !pBuffer )
return;
mPlayback = true;
Mix_PlayChannel( mChannel, pBuffer->mChunk, mCurrentSourcep->isLoop()?-1:0 );
Mix_Volume( mChannel, caclulateVolume());
}
void playSynced(LLAudioChannel *channelp) override
{
play();
}
void cleanup() override
{
if( mChannel < 0 )
return;
mPlayback = false;
Mix_HaltChannel( mChannel );
}
bool isPlaying() override
{
if( mChannel < 0 )
return false;
if( !mPlayback )
return false;
bool bRet = Mix_Playing( mChannel ) == 1;
if( !bRet )
{
mPlayback = false;
Mix_HaltChannel( mChannel );
}
return bRet;
}
bool updateBuffer() override
{
if( !mCurrentSourcep || mChannel < 0)
return false;
if( LLAudioChannel::updateBuffer() )
{
if(mCurrentSourcep)
{
startPlayback();
return true;
}
}
else if( mCurrentSourcep )
{
if( mPlayback )
Mix_Volume( mChannel, caclulateVolume());
}
else
{
if( mPlayback )
Mix_HaltChannel(mChannel);
mPlayback = false;
}
return mCurrentSourcep != 0;
}
void update3DPosition() override
{
if(!mCurrentSourcep || mChannel < 0)
return;
if(mPlayback)
{
auto pos = mEngine->getListenerPos();
LLVector3 soundpos{mCurrentSourcep->getPositionGlobal()};
F32 dist = dist_vec(soundpos, pos);
dist = llclamp(dist, 0.0f, 255.0f);
Mix_SetPosition(mChannel, 0, (uint8_t) dist);
}
}
void updateLoop() override
{
}
};
struct LLAudioEngineSDL2::ImplData
{
std::vector< uint8_t > mChannels;
bool mReady { false };
};
LLAudioEngineSDL2::LLAudioEngineSDL2()
{
}
LLAudioEngineSDL2::~LLAudioEngineSDL2()
{
}
bool LLAudioEngineSDL2::init(const S32 num_channels, void *userdata, const std::string &app_title)
{
LL_INFOS() << "Initializing SDL2 audio" << LL_ENDL;
mData = std::make_unique<ImplData>();
LLAudioEngine::init(num_channels, userdata, app_title);
int flags = MIX_INIT_FLAC | MIX_INIT_MP3 | MIX_INIT_MP3;
if( flags != Mix_Init( flags ) )
LL_WARNS() << "Mix_Init failed to intialize all formats: " << getMixerError () << LL_ENDL;
if( 0 != Mix_OpenAudio(44100, AUDIO_S16LSB, MIX_DEFAULT_CHANNELS, 1024 ) )
{
LL_WARNS() << "Mix_OpenAudio failed " << getMixerError() << LL_ENDL;
return false;
}
mData->mChannels.resize( num_channels+2 );
Mix_AllocateChannels( num_channels );
mData->mReady = true;
// Impl is stubbed out, but nothing more
// if (!getStreamingAudioImpl())
// setStreamingAudioImpl( new LLStreamingAudioSDL2() );
return true;
}
std::string LLAudioEngineSDL2::getDriverName(bool verbose)
{
return "SDL mixer";
}
void LLAudioEngineSDL2::shutdown()
{
Mix_CloseAudio();
Mix_Quit();
mData = nullptr;
}
void LLAudioEngineSDL2::updateWind(LLVector3 direction, F32 camera_height_above_water)
{
}
void LLAudioEngineSDL2::idle(F32 max_decode_time )
{
LLAudioEngine::idle(max_decode_time);
}
void LLAudioEngineSDL2::updateChannels()
{
LLAudioEngine::updateChannels();
}
LLAudioEngineSDL2::output_device_map_t LLAudioEngineSDL2::getDevices()
{
// Impl looks like this, but it might not be possible to support this right now
/*
if(mDevices.size())
return;
auto numDevices{SDL_GetNumAudioDevices(0)};
if( numDevices <= 0 )
return {};
LLAudioEngineSDL2::output_device_map_t devices{};
for( auto i{0}; i < numDevices; ++i )
{
auto pName{SDL_GetAudioDeviceName(i, 0)};
if( !pName )
continue;
std::string strName{ pName };
devices[ LLUUID::generateNewID( strName )] = strName;
}
*/
return mDevices;
}
void LLAudioEngineSDL2::setDevice(const LLUUID &device_uuid)
{
}
LLAudioBuffer *LLAudioEngineSDL2::createBuffer()
{
return new LLAudioBufferSDL2();
}
LLAudioChannel *LLAudioEngineSDL2::createChannel()
{
S32 channelNum{ -1 };
if( mData && mData->mReady )
{
for( S32 i = 0; i < mData->mChannels.size(); ++i )
{
if( !mData->mChannels[i])
{
channelNum = i;
mData->mChannels[i] = 1;
break;
}
}
if( channelNum == -1 )
{
channelNum = mData->mChannels.size();
mData->mChannels.push_back(0);
Mix_AllocateChannels( mData->mChannels.size() );
}
}
return new LLAudioChannelSDL2(channelNum, this);
}
void LLAudioEngineSDL2::deleteChannel(S32 aChannel)
{
if( !mData )
return;
if( aChannel < 0 || aChannel >= mData->mChannels.size() )
return;
mData->mChannels[aChannel] = 0;
}
bool LLAudioEngineSDL2::initWind()
{
// Not supported
return false;
}
void LLAudioEngineSDL2::cleanupWind()
{
}
void LLAudioEngineSDL2::setInternalGain(F32 gain)
{
}
void LLAudioEngineSDL2::allocateListener()
{
mListenerp = new LLListenerSDL2(this);
}