From 8276b1757063b17c8517abd5244b5719518a9005 Mon Sep 17 00:00:00 2001 From: rob-v Date: Mon, 5 Jun 2017 19:36:48 +0200 Subject: [PATCH] Fix Sound memory leak (OutOfMemoryException), remove Music caching to free memory --- OpenRA.Game/Sound/Sound.cs | 25 ++++++++-- OpenRA.Game/Sound/SoundDevice.cs | 4 +- OpenRA.Platforms.Default/OpenAlSoundEngine.cs | 49 +++++++++++++++++-- 3 files changed, 70 insertions(+), 8 deletions(-) diff --git a/OpenRA.Game/Sound/Sound.cs b/OpenRA.Game/Sound/Sound.cs index 64a651cc5e..5a776eed1b 100644 --- a/OpenRA.Game/Sound/Sound.cs +++ b/OpenRA.Game/Sound/Sound.cs @@ -39,6 +39,8 @@ namespace OpenRA readonly ISoundEngine soundEngine; Cache sounds; ISoundSource rawSource; + Func getSoundSource; + ISoundSource musicSource; ISound music; ISound video; MusicInfo currentMusic; @@ -81,9 +83,18 @@ namespace OpenRA public void Initialize(ISoundLoader[] loaders, IReadOnlyFileSystem fileSystem) { - sounds = new Cache(s => LoadSound(loaders, fileSystem, s)); + StopMusic(); + soundEngine.ReleaseSourcePool(); + + if (sounds != null) + foreach (var soundSource in sounds.Values) + soundSource.Dispose(); + + getSoundSource = s => LoadSound(loaders, fileSystem, s); + sounds = new Cache(getSoundSource); currentSounds = new Dictionary(); music = null; + musicSource = null; currentMusic = null; video = null; } @@ -195,11 +206,11 @@ namespace OpenRA StopMusic(); - var sound = sounds[m.Filename]; - if (sound == null) + musicSource = getSoundSource(m.Filename); + if (musicSource == null) return; - music = soundEngine.Play2D(sound, false, true, WPos.Zero, MusicVolume, false); + music = soundEngine.Play2D(musicSource, false, true, WPos.Zero, MusicVolume, false); currentMusic = m; MusicPlaying = true; } @@ -222,7 +233,13 @@ namespace OpenRA public void StopMusic() { if (music != null) + { soundEngine.StopSound(music); + soundEngine.ReleaseSound(music); + music = null; + musicSource.Dispose(); + musicSource = null; + } MusicPlaying = false; currentMusic = null; diff --git a/OpenRA.Game/Sound/SoundDevice.cs b/OpenRA.Game/Sound/SoundDevice.cs index cec1a2d8d0..3a96b09868 100644 --- a/OpenRA.Game/Sound/SoundDevice.cs +++ b/OpenRA.Game/Sound/SoundDevice.cs @@ -25,6 +25,8 @@ namespace OpenRA void StopAllSounds(); void SetListenerPosition(WPos position); void SetSoundVolume(float volume, ISound music, ISound video); + void ReleaseSourcePool(); + void ReleaseSound(ISound sound); } public class SoundDevice @@ -39,7 +41,7 @@ namespace OpenRA } } - public interface ISoundSource { } + public interface ISoundSource : IDisposable { } public interface ISound { diff --git a/OpenRA.Platforms.Default/OpenAlSoundEngine.cs b/OpenRA.Platforms.Default/OpenAlSoundEngine.cs index 2985aa8e42..8162c02de6 100644 --- a/OpenRA.Platforms.Default/OpenAlSoundEngine.cs +++ b/OpenRA.Platforms.Default/OpenAlSoundEngine.cs @@ -311,6 +311,26 @@ namespace OpenRA.Platforms.Default AL10.alListenerf(EFX.AL_METERS_PER_UNIT, .01f); } + public void ReleaseSourcePool() + { + foreach (var slot in sourcePool) + if (slot.Value.Sound != null) + ReleaseSound(slot.Key); + } + + public void ReleaseSound(ISound sound) + { + var openAlSound = sound as OpenAlSound; + if (openAlSound != null) + ReleaseSound(openAlSound.Source); + } + + void ReleaseSound(uint source) + { + AL10.alSourceStop(source); + AL10.alSourcei(source, AL10.AL_BUFFER, 0); + } + ~OpenAlSoundEngine() { Dispose(false); @@ -341,7 +361,10 @@ namespace OpenRA.Platforms.Default class OpenAlSoundSource : ISoundSource { - public readonly uint Buffer; + uint buffer; + bool disposed; + + public uint Buffer { get { return buffer; } } static int MakeALFormat(int channels, int bits) { @@ -353,8 +376,28 @@ namespace OpenRA.Platforms.Default public OpenAlSoundSource(byte[] data, int channels, int sampleBits, int sampleRate) { - AL10.alGenBuffers(new IntPtr(1), out Buffer); - AL10.alBufferData(Buffer, MakeALFormat(channels, sampleBits), data, new IntPtr(data.Length), new IntPtr(sampleRate)); + AL10.alGenBuffers(new IntPtr(1), out buffer); + AL10.alBufferData(buffer, MakeALFormat(channels, sampleBits), data, new IntPtr(data.Length), new IntPtr(sampleRate)); + } + + protected virtual void Dispose(bool disposing) + { + if (!disposed) + { + AL10.alDeleteBuffers(new IntPtr(1), ref buffer); + disposed = true; + } + } + + ~OpenAlSoundSource() + { + Dispose(false); + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); } }