Separate sound buffer pool from sound manager

pull/593/head
elsid 5 years ago
parent 2d61db555b
commit 944033db4e
No known key found for this signature in database
GPG Key ID: B845CB9FEE18AB40

@ -58,6 +58,7 @@ add_openmw_dir (mwscript
add_openmw_dir (mwsound
soundmanagerimp openal_output ffmpeg_decoder sound sound_buffer sound_decoder sound_output
loudness movieaudiofactory alext efx efx-presets regionsoundselector watersoundupdater volumesettings
sound_buffer
)
add_openmw_dir (mwworld

@ -7,6 +7,13 @@
namespace MWSound
{
// Extra play flags, not intended for caller use
enum PlayModeEx
{
Play_2D = 0,
Play_3D = 1 << 31,
};
// For testing individual PlayMode flags
inline int operator&(int a, PlayMode b) { return a & static_cast<int>(b); }
inline int operator&(PlayMode a, PlayMode b) { return static_cast<int>(a) & static_cast<int>(b); }

@ -0,0 +1,154 @@
#include "sound_buffer.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/world.hpp"
#include "../mwworld/esmstore.hpp"
#include <components/debug/debuglog.hpp>
#include <components/settings/settings.hpp>
#include <components/vfs/manager.hpp>
#include <algorithm>
#include <cmath>
namespace MWSound
{
namespace
{
struct AudioParams
{
float mAudioDefaultMinDistance;
float mAudioDefaultMaxDistance;
float mAudioMinDistanceMult;
float mAudioMaxDistanceMult;
};
AudioParams makeAudioParams(const MWBase::World& world)
{
const auto& settings = world.getStore().get<ESM::GameSetting>();
AudioParams params;
params.mAudioDefaultMinDistance = settings.find("fAudioDefaultMinDistance")->mValue.getFloat();
params.mAudioDefaultMaxDistance = settings.find("fAudioDefaultMaxDistance")->mValue.getFloat();
params.mAudioMinDistanceMult = settings.find("fAudioMinDistanceMult")->mValue.getFloat();
params.mAudioMaxDistanceMult = settings.find("fAudioMaxDistanceMult")->mValue.getFloat();
return params;
}
}
SoundBufferPool::SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output) :
mVfs(&vfs),
mOutput(&output),
mBufferCacheMax(std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1) * 1024 * 1024),
mBufferCacheMin(std::min(static_cast<std::size_t>(std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1)) * 1024 * 1024, mBufferCacheMax))
{
}
SoundBufferPool::~SoundBufferPool()
{
clear();
}
Sound_Buffer* SoundBufferPool::lookup(const std::string& soundId) const
{
const auto it = mBufferNameMap.find(soundId);
if (it != mBufferNameMap.end())
{
Sound_Buffer* sfx = it->second;
if (sfx->getHandle() != nullptr)
return sfx;
}
return nullptr;
}
Sound_Buffer* SoundBufferPool::load(const std::string& soundId)
{
if (mBufferNameMap.empty())
{
for (const ESM::Sound& sound : MWBase::Environment::get().getWorld()->getStore().get<ESM::Sound>())
insertSound(Misc::StringUtils::lowerCase(sound.mId), sound);
}
Sound_Buffer* sfx;
const auto it = mBufferNameMap.find(soundId);
if (it != mBufferNameMap.end())
sfx = it->second;
else
{
const ESM::Sound *sound = MWBase::Environment::get().getWorld()->getStore().get<ESM::Sound>().search(soundId);
if (sound == nullptr)
return {};
sfx = insertSound(soundId, *sound);
}
if (sfx->getHandle() == nullptr)
{
Sound_Handle handle;
size_t size;
std::tie(handle, size) = mOutput->loadSound(sfx->getResourceName());
if (handle == nullptr)
return {};
sfx->mHandle = handle;
mBufferCacheSize += size;
if (mBufferCacheSize > mBufferCacheMax)
{
unloadUnused();
if (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMax)
Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!";
}
mUnusedBuffers.push_front(sfx);
}
return sfx;
}
void SoundBufferPool::clear()
{
for (auto &sfx : mSoundBuffers)
{
if(sfx.mHandle)
mOutput->unloadSound(sfx.mHandle);
sfx.mHandle = nullptr;
}
mUnusedBuffers.clear();
}
Sound_Buffer* SoundBufferPool::insertSound(const std::string& soundId, const ESM::Sound& sound)
{
static const AudioParams audioParams = makeAudioParams(*MWBase::Environment::get().getWorld());
float volume = static_cast<float>(std::pow(10.0, (sound.mData.mVolume / 255.0 * 3348.0 - 3348.0) / 2000.0));
float min = sound.mData.mMinRange;
float max = sound.mData.mMaxRange;
if (min == 0 && max == 0)
{
min = audioParams.mAudioDefaultMinDistance;
max = audioParams.mAudioDefaultMaxDistance;
}
min *= audioParams.mAudioMinDistanceMult;
max *= audioParams.mAudioMaxDistanceMult;
min = std::max(min, 1.0f);
max = std::max(min, max);
Sound_Buffer& sfx = mSoundBuffers.emplace_back("Sound/" + sound.mSound, volume, min, max);
mVfs->normalizeFilename(sfx.mResourceName);
mBufferNameMap.emplace(soundId, &sfx);
return &sfx;
}
void SoundBufferPool::unloadUnused()
{
while (!mUnusedBuffers.empty() && mBufferCacheSize > mBufferCacheMin)
{
Sound_Buffer* const unused = mUnusedBuffers.back();
mBufferCacheSize -= mOutput->unloadSound(unused->getHandle());
unused->mHandle = nullptr;
mUnusedBuffers.pop_back();
}
}
}

@ -1,27 +1,100 @@
#ifndef GAME_SOUND_SOUND_BUFFER_H
#define GAME_SOUND_SOUND_BUFFER_H
#include <algorithm>
#include <string>
#include <deque>
#include <unordered_map>
#include "sound_output.hpp"
namespace ESM
{
struct Sound;
}
namespace VFS
{
class Manager;
}
namespace MWSound
{
class SoundBufferPool;
class Sound_Buffer
{
public:
std::string mResourceName;
public:
Sound_Buffer(std::string resname, float volume, float mindist, float maxdist)
: mResourceName(std::move(resname)), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist)
{}
const std::string& getResourceName() const noexcept { return mResourceName; }
Sound_Handle getHandle() const noexcept { return mHandle; }
float getVolume() const noexcept { return mVolume; }
float getMinDist() const noexcept { return mMinDist; }
float getMaxDist() const noexcept { return mMaxDist; }
private:
std::string mResourceName;
float mVolume = 0;
float mMinDist = 0;
float mMaxDist = 0;
Sound_Handle mHandle = nullptr;
std::size_t mUses = 0;
friend class SoundBufferPool;
};
class SoundBufferPool
{
public:
SoundBufferPool(const VFS::Manager& vfs, Sound_Output& output);
SoundBufferPool(const SoundBufferPool&) = delete;
~SoundBufferPool();
Sound_Buffer* lookup(const std::string& soundId) const;
Sound_Buffer* load(const std::string& soundId);
void use(Sound_Buffer& sfx)
{
if (sfx.mUses++ == 0)
{
const auto it = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), &sfx);
if (it != mUnusedBuffers.end())
mUnusedBuffers.erase(it);
}
}
void release(Sound_Buffer& sfx)
{
if (--sfx.mUses == 0)
mUnusedBuffers.push_front(&sfx);
}
float mVolume;
float mMinDist, mMaxDist;
void clear();
Sound_Handle mHandle;
private:
const VFS::Manager* const mVfs;
Sound_Output* mOutput;
std::deque<Sound_Buffer> mSoundBuffers;
std::unordered_map<std::string, Sound_Buffer*> mBufferNameMap;
std::size_t mBufferCacheMax;
std::size_t mBufferCacheMin;
std::size_t mBufferCacheSize = 0;
// NOTE: unused buffers are stored in front-newest order.
std::deque<Sound_Buffer*> mUnusedBuffers;
size_t mUses;
inline Sound_Buffer* insertSound(const std::string& soundId, const ESM::Sound& sound);
Sound_Buffer(std::string resname, float volume, float mindist, float maxdist)
: mResourceName(resname), mVolume(volume), mMinDist(mindist), mMaxDist(maxdist), mHandle(nullptr), mUses(0)
{ }
inline void unloadUnused();
};
}

@ -5,7 +5,7 @@
#include <memory>
#include <vector>
#include "soundmanagerimp.hpp"
#include "../mwbase/soundmanager.hpp"
namespace MWSound
{
@ -25,6 +25,12 @@ namespace MWSound
Auto
};
enum Environment
{
Env_Normal,
Env_Underwater
};
class Sound_Output
{
SoundManager &mManager;
@ -81,6 +87,7 @@ namespace MWSound
friend class OpenAL_Output;
friend class SoundManager;
friend class SoundBufferPool;
};
}

@ -56,8 +56,7 @@ namespace MWSound
: mVFS(vfs)
, mOutput(new DEFAULT_OUTPUT(*this))
, mWaterSoundUpdater(makeWaterSoundUpdaterSettings())
, mSoundBuffers(new SoundBufferList::element_type())
, mBufferCacheSize(0)
, mSoundBuffers(*vfs, *mOutput)
, mListenerUnderwater(false)
, mListenerPos(0,0,0)
, mListenerDir(1,0,0)
@ -69,11 +68,6 @@ namespace MWSound
, mLastCell(nullptr)
, mCurrentRegionSound(nullptr)
{
mBufferCacheMin = std::max(Settings::Manager::getInt("buffer cache min", "Sound"), 1);
mBufferCacheMax = std::max(Settings::Manager::getInt("buffer cache max", "Sound"), 1);
mBufferCacheMax *= 1024*1024;
mBufferCacheMin = std::min(mBufferCacheMin*1024*1024, mBufferCacheMax);
if(!useSound)
{
Log(Debug::Info) << "Sound disabled.";
@ -116,13 +110,7 @@ namespace MWSound
SoundManager::~SoundManager()
{
clear();
for(Sound_Buffer &sfx : *mSoundBuffers)
{
if(sfx.mHandle)
mOutput->unloadSound(sfx.mHandle);
sfx.mHandle = nullptr;
}
mUnusedBuffers.clear();
mSoundBuffers.clear();
mOutput.reset();
}
@ -132,112 +120,18 @@ namespace MWSound
return DecoderPtr(new DEFAULT_DECODER (mVFS));
}
Sound_Buffer *SoundManager::insertSound(const std::string &soundId, const ESM::Sound *sound)
{
MWBase::World* world = MWBase::Environment::get().getWorld();
static const float fAudioDefaultMinDistance = world->getStore().get<ESM::GameSetting>().find("fAudioDefaultMinDistance")->mValue.getFloat();
static const float fAudioDefaultMaxDistance = world->getStore().get<ESM::GameSetting>().find("fAudioDefaultMaxDistance")->mValue.getFloat();
static const float fAudioMinDistanceMult = world->getStore().get<ESM::GameSetting>().find("fAudioMinDistanceMult")->mValue.getFloat();
static const float fAudioMaxDistanceMult = world->getStore().get<ESM::GameSetting>().find("fAudioMaxDistanceMult")->mValue.getFloat();
float volume, min, max;
volume = static_cast<float>(pow(10.0, (sound->mData.mVolume / 255.0*3348.0 - 3348.0) / 2000.0));
min = sound->mData.mMinRange;
max = sound->mData.mMaxRange;
if (min == 0 && max == 0)
{
min = fAudioDefaultMinDistance;
max = fAudioDefaultMaxDistance;
}
min *= fAudioMinDistanceMult;
max *= fAudioMaxDistanceMult;
min = std::max(min, 1.0f);
max = std::max(min, max);
Sound_Buffer *sfx = &*mSoundBuffers->insert(mSoundBuffers->end(),
Sound_Buffer("Sound/"+sound->mSound, volume, min, max)
);
mVFS->normalizeFilename(sfx->mResourceName);
mBufferNameMap.insert(std::make_pair(soundId, sfx));
return sfx;
}
// Lookup a soundId for its sound data (resource name, local volume,
// minRange, and maxRange)
Sound_Buffer *SoundManager::lookupSound(const std::string &soundId) const
{
NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId);
if(snd != mBufferNameMap.end())
{
Sound_Buffer *sfx = snd->second;
if(sfx->mHandle) return sfx;
}
return nullptr;
return mSoundBuffers.lookup(soundId);
}
// Lookup a soundId for its sound data (resource name, local volume,
// minRange, and maxRange), and ensure it's ready for use.
Sound_Buffer *SoundManager::loadSound(const std::string &soundId)
{
#ifdef __GNUC__
#define LIKELY(x) __builtin_expect((bool)(x), true)
#define UNLIKELY(x) __builtin_expect((bool)(x), false)
#else
#define LIKELY(x) (bool)(x)
#define UNLIKELY(x) (bool)(x)
#endif
if(UNLIKELY(mBufferNameMap.empty()))
{
MWBase::World *world = MWBase::Environment::get().getWorld();
for(const ESM::Sound &sound : world->getStore().get<ESM::Sound>())
insertSound(Misc::StringUtils::lowerCase(sound.mId), &sound);
}
Sound_Buffer *sfx;
NameBufferMap::const_iterator snd = mBufferNameMap.find(soundId);
if(LIKELY(snd != mBufferNameMap.end()))
sfx = snd->second;
else
{
MWBase::World *world = MWBase::Environment::get().getWorld();
const ESM::Sound *sound = world->getStore().get<ESM::Sound>().search(soundId);
if(!sound) return nullptr;
sfx = insertSound(soundId, sound);
}
#undef LIKELY
#undef UNLIKELY
if(!sfx->mHandle)
{
size_t size;
std::tie(sfx->mHandle, size) = mOutput->loadSound(sfx->mResourceName);
if(!sfx->mHandle) return nullptr;
mBufferCacheSize += size;
if(mBufferCacheSize > mBufferCacheMax)
{
do {
if(mUnusedBuffers.empty())
{
Log(Debug::Warning) << "No unused sound buffers to free, using " << mBufferCacheSize << " bytes!";
break;
}
Sound_Buffer *unused = mUnusedBuffers.back();
size = mOutput->unloadSound(unused->mHandle);
mBufferCacheSize -= size;
unused->mHandle = nullptr;
mUnusedBuffers.pop_back();
} while(mBufferCacheSize > mBufferCacheMin);
}
mUnusedBuffers.push_front(sfx);
}
return sfx;
return mSoundBuffers.load(soundId);
}
DecoderPtr SoundManager::loadVoice(const std::string &voicefile)
@ -624,23 +518,18 @@ namespace MWSound
SoundPtr sound = getSoundRef();
sound->init([&] {
SoundParams params;
params.mVolume = volume * sfx->mVolume;
params.mVolume = volume * sfx->getVolume();
params.mBaseVolume = volumeFromType(type);
params.mPitch = pitch;
params.mFlags = mode | type | Play_2D;
return params;
} ());
if(!mOutput->playSound(sound.get(), sfx->mHandle, offset))
if(!mOutput->playSound(sound.get(), sfx->getHandle(), offset))
return nullptr;
if(sfx->mUses++ == 0)
{
SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx);
if(iter != mUnusedBuffers.end())
mUnusedBuffers.erase(iter);
}
Sound* result = sound.get();
mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx);
mSoundBuffers.use(*sfx);
return result;
}
@ -668,40 +557,35 @@ namespace MWSound
{
sound->init([&] {
SoundParams params;
params.mVolume = volume * sfx->mVolume;
params.mVolume = volume * sfx->getVolume();
params.mBaseVolume = volumeFromType(type);
params.mPitch = pitch;
params.mFlags = mode | type | Play_2D;
return params;
} ());
played = mOutput->playSound(sound.get(), sfx->mHandle, offset);
played = mOutput->playSound(sound.get(), sfx->getHandle(), offset);
}
else
{
sound->init([&] {
SoundParams params;
params.mPos = objpos;
params.mVolume = volume * sfx->mVolume;
params.mVolume = volume * sfx->getVolume();
params.mBaseVolume = volumeFromType(type);
params.mPitch = pitch;
params.mMinDistance = sfx->mMinDist;
params.mMaxDistance = sfx->mMaxDist;
params.mMinDistance = sfx->getMinDist();
params.mMaxDistance = sfx->getMaxDist();
params.mFlags = mode | type | Play_3D;
return params;
} ());
played = mOutput->playSound3D(sound.get(), sfx->mHandle, offset);
played = mOutput->playSound3D(sound.get(), sfx->getHandle(), offset);
}
if(!played)
return nullptr;
if(sfx->mUses++ == 0)
{
SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx);
if(iter != mUnusedBuffers.end())
mUnusedBuffers.erase(iter);
}
Sound* result = sound.get();
mActiveSounds[ptr].emplace_back(std::move(sound), sfx);
mSoundBuffers.use(*sfx);
return result;
}
@ -720,25 +604,20 @@ namespace MWSound
sound->init([&] {
SoundParams params;
params.mPos = initialPos;
params.mVolume = volume * sfx->mVolume;
params.mVolume = volume * sfx->getVolume();
params.mBaseVolume = volumeFromType(type);
params.mPitch = pitch;
params.mMinDistance = sfx->mMinDist;
params.mMaxDistance = sfx->mMaxDist;
params.mMinDistance = sfx->getMinDist();
params.mMaxDistance = sfx->getMaxDist();
params.mFlags = mode | type | Play_3D;
return params;
} ());
if(!mOutput->playSound3D(sound.get(), sfx->mHandle, offset))
if(!mOutput->playSound3D(sound.get(), sfx->getHandle(), offset))
return nullptr;
if(sfx->mUses++ == 0)
{
SoundList::iterator iter = std::find(mUnusedBuffers.begin(), mUnusedBuffers.end(), sfx);
if(iter != mUnusedBuffers.end())
mUnusedBuffers.erase(iter);
}
Sound* result = sound.get();
mActiveSounds[MWWorld::ConstPtr()].emplace_back(std::move(sound), sfx);
mSoundBuffers.use(*sfx);
return result;
}
@ -921,7 +800,7 @@ namespace MWSound
case WaterSoundAction::DoNothing:
break;
case WaterSoundAction::SetVolume:
mNearWaterSound->setVolume(update.mVolume * sfx->mVolume);
mNearWaterSound->setVolume(update.mVolume * sfx->getVolume());
break;
case WaterSoundAction::FinishSound:
mOutput->finishSound(mNearWaterSound);
@ -1028,7 +907,6 @@ namespace MWSound
while(sndidx != snditer->second.end())
{
Sound *sound = sndidx->first.get();
Sound_Buffer *sfx = sndidx->second;
if(!ptr.isEmpty() && sound->getIs3D())
{
@ -1050,8 +928,7 @@ namespace MWSound
mUnderwaterSound = nullptr;
if (sound == mNearWaterSound)
mNearWaterSound = nullptr;
if(sfx->mUses-- == 1)
mUnusedBuffers.push_front(sfx);
mSoundBuffers.release(*sndidx->second);
sndidx = snditer->second.erase(sndidx);
}
else
@ -1313,9 +1190,7 @@ namespace MWSound
for(SoundBufferRefPair &sndbuf : snd.second)
{
mOutput->finishSound(sndbuf.first.get());
Sound_Buffer *sfx = sndbuf.second;
if(sfx->mUses-- == 1)
mUnusedBuffers.push_front(sfx);
mSoundBuffers.release(*sndbuf.second);
}
}
mActiveSounds.clear();

@ -4,7 +4,6 @@
#include <memory>
#include <string>
#include <utility>
#include <deque>
#include <map>
#include <unordered_map>
@ -18,6 +17,7 @@
#include "watersoundupdater.hpp"
#include "type.hpp"
#include "volumesettings.hpp"
#include "sound_buffer.hpp"
namespace VFS
{
@ -36,17 +36,6 @@ namespace MWSound
struct Sound_Decoder;
class Sound;
class Stream;
class Sound_Buffer;
enum Environment {
Env_Normal,
Env_Underwater
};
// Extra play flags, not intended for caller use
enum PlayModeEx {
Play_2D = 0,
Play_3D = 1<<31
};
using SoundPtr = Misc::ObjectPtr<Sound>;
using StreamPtr = Misc::ObjectPtr<Stream>;
@ -66,21 +55,7 @@ namespace MWSound
WaterSoundUpdater mWaterSoundUpdater;
typedef std::unique_ptr<std::deque<Sound_Buffer> > SoundBufferList;
// List of sound buffers, grown as needed. New enties are added to the
// back, allowing existing Sound_Buffer references/pointers to remain
// valid.
SoundBufferList mSoundBuffers;
size_t mBufferCacheMin;
size_t mBufferCacheMax;
size_t mBufferCacheSize;
typedef std::unordered_map<std::string,Sound_Buffer*> NameBufferMap;
NameBufferMap mBufferNameMap;
// NOTE: unused buffers are stored in front-newest order.
typedef std::deque<Sound_Buffer*> SoundList;
SoundList mUnusedBuffers;
SoundBufferPool mSoundBuffers;
Misc::ObjectPool<Sound> mSounds;

Loading…
Cancel
Save