UpdatingSourceVoice + warn on audio leak

pull/53/head
cosmonaut 2023-11-20 18:56:22 -08:00
parent 772a0378bb
commit e961a18a83
14 changed files with 83 additions and 52 deletions

View File

@ -64,7 +64,7 @@ namespace MoonWorks.Audio
};
}
protected override unsafe void Destroy()
protected override unsafe void DisposeUnmanagedState()
{
if (OwnsBufferData)
{

View File

@ -90,22 +90,15 @@ namespace MoonWorks.Audio
/// <summary>
/// Unloads the Ogg data, freeing resources.
/// </summary>
public override unsafe void Unload()
public override void Unload()
{
if (Loaded)
{
FAudio.stb_vorbis_close(VorbisHandle);
NativeMemory.Free((void*) FileDataPtr);
VorbisHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
DisposeUnmanagedState();
}
/// <summary>
/// Loads an entire ogg file into an AudioBuffer. Useful for static audio.
/// </summary>
public unsafe static AudioBuffer CreateBuffer(AudioDevice device, string filePath)
public static unsafe AudioBuffer CreateBuffer(AudioDevice device, string filePath)
{
var filePointer = FAudio.stb_vorbis_open_filename(filePath, out var error, IntPtr.Zero);
@ -143,5 +136,17 @@ namespace MoonWorks.Audio
(uint) lengthInBytes,
true);
}
protected override unsafe void DisposeUnmanagedState()
{
if (Loaded)
{
FAudio.stb_vorbis_close(VorbisHandle);
NativeMemory.Free((void*) FileDataPtr);
VorbisHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
}
}
}

View File

@ -99,16 +99,9 @@ namespace MoonWorks.Audio
/// <summary>
/// Unloads the qoa data, freeing resources.
/// </summary>
public override unsafe void Unload()
public override void Unload()
{
if (Loaded)
{
FAudio.qoa_close(QoaHandle);
NativeMemory.Free((void*) FileDataPtr);
QoaHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
DisposeUnmanagedState();
}
/// <summary>
@ -160,5 +153,17 @@ namespace MoonWorks.Audio
((UInt64)(bytes[4]) << 24) | ((UInt64)(bytes[5]) << 16) |
((UInt64)(bytes[6]) << 8) | ((UInt64)(bytes[7]) << 0);
}
protected override unsafe void DisposeUnmanagedState()
{
if (Loaded)
{
FAudio.qoa_close(QoaHandle);
NativeMemory.Free((void*) FileDataPtr);
QoaHandle = IntPtr.Zero;
FileDataPtr = IntPtr.Zero;
}
}
}
}

View File

@ -36,10 +36,5 @@ namespace MoonWorks.Audio
/// <param name="filledLengthInBytes">How much data was actually filled in by the decode.</param>
/// <param name="reachedEnd">Whether the end of the data was reached on this decode.</param>
public abstract unsafe void Decode(void* buffer, int bufferLengthInBytes, out int filledLengthInBytes, out bool reachedEnd);
protected override void Destroy()
{
Unload();
}
}
}

View File

@ -26,7 +26,7 @@ namespace MoonWorks.Audio
public float SpeedOfSound = 343.5f;
private readonly HashSet<GCHandle> resources = new HashSet<GCHandle>();
private readonly HashSet<SourceVoice> activeSourceVoices = new HashSet<SourceVoice>();
private readonly HashSet<UpdatingSourceVoice> updatingSourceVoices = new HashSet<UpdatingSourceVoice>();
private AudioTweenManager AudioTweenManager;
@ -42,7 +42,7 @@ namespace MoonWorks.Audio
internal readonly object StateLock = new object();
private bool Running;
private bool IsDisposed;
public bool IsDisposed { get; private set; }
internal unsafe AudioDevice()
{
@ -164,15 +164,19 @@ namespace MoonWorks.Audio
AudioTweenManager.Update(elapsedSeconds);
foreach (var voice in activeSourceVoices)
foreach (var voice in updatingSourceVoices)
{
voice.Update();
}
foreach (var voice in VoicesToReturn)
{
if (voice is UpdatingSourceVoice updatingSourceVoice)
{
updatingSourceVoices.Remove(updatingSourceVoice);
}
voice.Reset();
activeSourceVoices.Remove(voice);
VoicePool.Return(voice);
}
@ -197,7 +201,12 @@ namespace MoonWorks.Audio
lock (StateLock)
{
var voice = VoicePool.Obtain<T>(format);
activeSourceVoices.Add(voice);
if (voice is UpdatingSourceVoice updatingSourceVoice)
{
updatingSourceVoices.Add(updatingSourceVoice);
}
return voice;
}
}
@ -258,9 +267,9 @@ namespace MoonWorks.Audio
{
resources.Add(resourceReference);
if (resourceReference.Target is SourceVoice voice)
if (resourceReference.Target is UpdatingSourceVoice updatableVoice)
{
activeSourceVoices.Add(voice);
updatingSourceVoices.Add(updatableVoice);
}
}
}

View File

@ -132,7 +132,5 @@ namespace MoonWorks.Audio
emitterData.pReverbCurve = IntPtr.Zero;
emitterData.CurveDistanceScaler = 1.0f;
}
protected override void Destroy() { }
}
}

View File

@ -94,7 +94,5 @@ namespace MoonWorks.Audio
/* Unexposed variables, defaults based on XNA behavior */
listenerData.pCone = IntPtr.Zero;
}
protected override void Destroy() { }
}
}

View File

@ -19,16 +19,22 @@ namespace MoonWorks.Audio
Device.AddResourceReference(SelfReference);
}
protected abstract void Destroy();
protected virtual void DisposeManagedState() { }
protected virtual void DisposeUnmanagedState() { }
protected void Dispose(bool disposing)
{
if (!IsDisposed)
{
Destroy();
if (disposing)
{
DisposeManagedState();
Device.RemoveResourceReference(SelfReference);
SelfReference.Free();
Device.RemoveResourceReference(SelfReference);
SelfReference.Free();
}
DisposeUnmanagedState();
IsDisposed = true;
}
@ -36,6 +42,17 @@ namespace MoonWorks.Audio
~AudioResource()
{
#if DEBUG
// If the graphics device associated with this resource was already disposed, we assume
// that your game is in the middle of shutting down.
if (!IsDisposed && Device != null && !Device.IsDisposed)
{
// If you see this log message, you leaked a graphics resource without disposing it!
// This means your game may eventually run out of native memory for mysterious reasons.
Logger.LogWarn($"A resource of type {GetType().Name} was not Disposed.");
}
#endif
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
Dispose(disposing: false);
}

View File

@ -3,7 +3,7 @@ namespace MoonWorks.Audio
/// <summary>
/// Plays back a series of AudioBuffers in sequence. Set the OnSoundNeeded callback to add AudioBuffers dynamically.
/// </summary>
public class SoundSequence : SourceVoice, IPoolable<SoundSequence>
public class SoundSequence : UpdatingSourceVoice, IPoolable<SoundSequence>
{
public int NeedSoundThreshold = 0;
public delegate void OnSoundNeededFunc();

View File

@ -191,12 +191,6 @@ namespace MoonWorks.Audio
Device.Return(this);
}
/// <summary>
/// Called automatically by AudioDevice in the audio thread.
/// Don't call this yourself! You might regret it!
/// </summary>
public virtual void Update() { }
/// <summary>
/// Adds an FAudio buffer to the voice queue.
/// The voice processes and plays back the buffers in its queue in the order that they were submitted.
@ -221,10 +215,9 @@ namespace MoonWorks.Audio
base.Reset();
}
protected override unsafe void Destroy()
protected override void DisposeManagedState()
{
Stop();
base.Destroy();
}
}
}

View File

@ -6,7 +6,7 @@ namespace MoonWorks.Audio
/// <summary>
/// Use in conjunction with an AudioDataStreamable object to play back streaming audio data.
/// </summary>
public class StreamingVoice : SourceVoice, IPoolable<StreamingVoice>
public class StreamingVoice : UpdatingSourceVoice, IPoolable<StreamingVoice>
{
private const int BUFFER_COUNT = 3;
private readonly IntPtr[] buffers;

View File

@ -4,7 +4,7 @@ namespace MoonWorks.Audio
/// TransientVoice is intended for playing one-off sound effects that don't have a long term reference. <br/>
/// It will be automatically returned to the AudioDevice SourceVoice pool once it is done playing back.
/// </summary>
public class TransientVoice : SourceVoice, IPoolable<TransientVoice>
public class TransientVoice : UpdatingSourceVoice, IPoolable<TransientVoice>
{
static TransientVoice IPoolable<TransientVoice>.Create(AudioDevice device, Format format)
{

View File

@ -0,0 +1,11 @@
namespace MoonWorks.Audio
{
public abstract class UpdatingSourceVoice : SourceVoice
{
protected UpdatingSourceVoice(AudioDevice device, Format format) : base(device, format)
{
}
public abstract void Update();
}
}

View File

@ -565,7 +565,7 @@ namespace MoonWorks.Audio
);
}
protected unsafe override void Destroy()
protected override void DisposeUnmanagedState()
{
NativeMemory.Free(pMatrixCoefficients);
FAudio.FAudioVoice_DestroyVoice(Handle);