From 8aa9ce550db94f143ea415d8c63ef013c6ab84ff Mon Sep 17 00:00:00 2001 From: cosmonaut Date: Fri, 3 Mar 2023 23:56:24 -0800 Subject: [PATCH] fix audio thread deadlocks --- src/Audio/AudioDevice.cs | 29 ++++----- src/Audio/AudioResource.cs | 2 +- src/Audio/StreamingSound.cs | 22 ++++--- src/Audio/StreamingSoundOgg.cs | 9 ++- src/Video/StreamingSoundTheora.cs | 20 ++++--- src/Video/Video.cs | 6 +- src/Video/VideoPlayer.cs | 99 ++++++++++++++++++------------- 7 files changed, 111 insertions(+), 76 deletions(-) diff --git a/src/Audio/AudioDevice.cs b/src/Audio/AudioDevice.cs index f4512d6..6d08ae3 100644 --- a/src/Audio/AudioDevice.cs +++ b/src/Audio/AudioDevice.cs @@ -28,7 +28,7 @@ namespace MoonWorks.Audio } private readonly List> resources = new List>(); - private readonly List> streamingSounds = new List>(); + private readonly List streamingSounds = new List(); private AudioTweenPool AudioTweenPool = new AudioTweenPool(); private readonly List AudioTweens = new List(); @@ -39,7 +39,7 @@ namespace MoonWorks.Audio private long previousTickTime; private Thread Thread; private AutoResetEvent WakeSignal; - private readonly object StateLock = new object(); + internal readonly object StateLock = new object(); private bool IsDisposed; @@ -152,15 +152,7 @@ namespace MoonWorks.Audio for (var i = streamingSounds.Count - 1; i >= 0; i--) { - var weakReference = streamingSounds[i]; - if (weakReference.TryGetTarget(out var streamingSound)) - { - streamingSound.Update(); - } - else - { - streamingSounds.RemoveAt(i); - } + streamingSounds[i].Update(); } for (var i = AudioTweens.Count - 1; i >= 0; i--) @@ -262,7 +254,12 @@ namespace MoonWorks.Audio private void AddDynamicSoundInstance(StreamingSound instance) { - streamingSounds.Add(new WeakReference(instance)); + streamingSounds.Add(instance); + } + + private void RemoveDynamicSoundInstance(StreamingSound reference) + { + streamingSounds.Remove(reference); } internal void AddResourceReference(AudioResource resource, WeakReference resourceReference) @@ -278,11 +275,16 @@ namespace MoonWorks.Audio } } - internal void RemoveResourceReference(WeakReference resourceReference) + internal void RemoveResourceReference(AudioResource resource, WeakReference resourceReference) { lock (StateLock) { resources.Remove(resourceReference); + + if (resource is StreamingSound streamingSound) + { + RemoveDynamicSoundInstance(streamingSound); + } } } @@ -314,7 +316,6 @@ namespace MoonWorks.Audio } } - // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources ~AudioDevice() { // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method diff --git a/src/Audio/AudioResource.cs b/src/Audio/AudioResource.cs index aa83763..babcde7 100644 --- a/src/Audio/AudioResource.cs +++ b/src/Audio/AudioResource.cs @@ -28,7 +28,7 @@ namespace MoonWorks.Audio if (selfReference != null) { - Device.RemoveResourceReference(selfReference); + Device.RemoveResourceReference(this, selfReference); selfReference = null; } diff --git a/src/Audio/StreamingSound.cs b/src/Audio/StreamingSound.cs index 059a2c9..023dd97 100644 --- a/src/Audio/StreamingSound.cs +++ b/src/Audio/StreamingSound.cs @@ -92,12 +92,15 @@ namespace MoonWorks.Audio { lock (StateLock) { - if (State != SoundState.Playing) + if (!IsDisposed) { - return; - } + if (State != SoundState.Playing) + { + return; + } - QueueBuffers(); + QueueBuffers(); + } } } @@ -182,11 +185,14 @@ namespace MoonWorks.Audio { lock (StateLock) { - StopImmediate(); - - for (int i = 0; i < BUFFER_COUNT; i += 1) + if (!IsDisposed) { - NativeMemory.Free((void*) buffers[i]); + StopImmediate(); + + for (int i = 0; i < BUFFER_COUNT; i += 1) + { + NativeMemory.Free((void*) buffers[i]); + } } } } diff --git a/src/Audio/StreamingSoundOgg.cs b/src/Audio/StreamingSoundOgg.cs index e258ec1..bb177a9 100644 --- a/src/Audio/StreamingSoundOgg.cs +++ b/src/Audio/StreamingSoundOgg.cs @@ -89,8 +89,13 @@ namespace MoonWorks.Audio protected unsafe override void Destroy() { - FAudio.stb_vorbis_close(VorbisHandle); - NativeMemory.Free((void*) FileDataPtr); + base.Destroy(); + + if (!IsDisposed) + { + FAudio.stb_vorbis_close(VorbisHandle); + NativeMemory.Free((void*) FileDataPtr); + } } } } diff --git a/src/Video/StreamingSoundTheora.cs b/src/Video/StreamingSoundTheora.cs index 41ddca3..04d999f 100644 --- a/src/Video/StreamingSoundTheora.cs +++ b/src/Video/StreamingSoundTheora.cs @@ -32,14 +32,20 @@ namespace MoonWorks.Video ) { var lengthInFloats = bufferLengthInBytes / sizeof(float); - int samples = Theorafile.tf_readaudio( - VideoHandle, - (IntPtr) buffer, - lengthInFloats - ); + // FIXME: this gets gnarly with theorafile being not thread safe + // is there some way we could just manually update in VideoPlayer + // instead of going through AudioDevice? + lock (Device.StateLock) + { + int samples = Theorafile.tf_readaudio( + VideoHandle, + (IntPtr) buffer, + lengthInFloats + ); - filledLengthInBytes = samples * sizeof(float); - reachedEnd = Theorafile.tf_eos(VideoHandle) == 1; + filledLengthInBytes = samples * sizeof(float); + reachedEnd = Theorafile.tf_eos(VideoHandle) == 1; + } } protected override void OnReachedEnd() { } diff --git a/src/Video/Video.cs b/src/Video/Video.cs index 592da2b..0c7b696 100644 --- a/src/Video/Video.cs +++ b/src/Video/Video.cs @@ -27,7 +27,7 @@ namespace MoonWorks.Video private int yWidth; private int yHeight; - private bool disposed; + private bool IsDisposed; public Video(string filename) { @@ -89,7 +89,7 @@ namespace MoonWorks.Video protected virtual void Dispose(bool disposing) { - if (!disposed) + if (!IsDisposed) { if (disposing) { @@ -100,7 +100,7 @@ namespace MoonWorks.Video Theorafile.tf_close(ref Handle); NativeMemory.Free(videoData); - disposed = true; + IsDisposed = true; } } diff --git a/src/Video/VideoPlayer.cs b/src/Video/VideoPlayer.cs index 9269153..59f3c3d 100644 --- a/src/Video/VideoPlayer.cs +++ b/src/Video/VideoPlayer.cs @@ -181,18 +181,22 @@ namespace MoonWorks.Video lastTimestamp = 0; timeElapsed = 0; - if (audioStream != null) + lock (AudioDevice.StateLock) { - audioStream.StopImmediate(); - audioStream.Dispose(); - audioStream = null; + DestroyAudioStream(); + + Theorafile.tf_reset(Video.Handle); } - Theorafile.tf_reset(Video.Handle); - State = VideoState.Stopped; } + public void Unload() + { + Stop(); + Video = null; + } + public void Render() { if (Video == null || State == VideoState.Stopped) @@ -200,48 +204,48 @@ namespace MoonWorks.Video return; } - timeElapsed += (timer.Elapsed.TotalMilliseconds - lastTimestamp) * PlaybackSpeed; - lastTimestamp = timer.Elapsed.TotalMilliseconds; - - int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond))); - if (thisFrame > currentFrame) + // Theorafile is not thread safe so we have to do this. Fun! + lock (AudioDevice.StateLock) { - if (Theorafile.tf_readvideo( - Video.Handle, - (IntPtr) yuvData, - thisFrame - currentFrame - ) == 1 || currentFrame == -1) { - UpdateRenderTexture(); + timeElapsed += (timer.Elapsed.TotalMilliseconds - lastTimestamp) * PlaybackSpeed; + lastTimestamp = timer.Elapsed.TotalMilliseconds; + + int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond))); + if (thisFrame > currentFrame) + { + if (Theorafile.tf_readvideo( + Video.Handle, + (IntPtr) yuvData, + thisFrame - currentFrame + ) == 1 || currentFrame == -1) + { + UpdateRenderTexture(); + } + + currentFrame = thisFrame; } - currentFrame = thisFrame; - } - - bool ended = Theorafile.tf_eos(Video.Handle) == 1; - if (ended) - { - timer.Stop(); - timer.Reset(); - - if (audioStream != null) + bool ended = Theorafile.tf_eos(Video.Handle) == 1; + if (ended) { - audioStream.Stop(); - audioStream.Dispose(); - audioStream = null; - } + timer.Stop(); + timer.Reset(); - Theorafile.tf_reset(Video.Handle); + DestroyAudioStream(); - if (Loop) - { - // Start over! - InitializeTheoraStream(); + Theorafile.tf_reset(Video.Handle); - timer.Start(); - } - else - { - State = VideoState.Stopped; + if (Loop) + { + // Start over! + InitializeTheoraStream(); + + timer.Start(); + } + else + { + State = VideoState.Stopped; + } } } } @@ -306,14 +310,27 @@ namespace MoonWorks.Video // Grab the first bit of audio. We're trying to start the decoding ASAP. if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1) { + DestroyAudioStream(); + int channels, sampleRate; Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate); + audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate); } currentFrame = -1; } + private void DestroyAudioStream() + { + if (audioStream != null) + { + audioStream.StopImmediate(); + audioStream.Dispose(); + audioStream = null; + } + } + protected virtual void Dispose(bool disposing) { if (!disposed)