fix audio thread deadlocks

pull/47/head
cosmonaut 2023-03-03 23:56:24 -08:00
parent 52c4fa26c7
commit 8aa9ce550d
7 changed files with 111 additions and 76 deletions

View File

@ -28,7 +28,7 @@ namespace MoonWorks.Audio
} }
private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>(); private readonly List<WeakReference<AudioResource>> resources = new List<WeakReference<AudioResource>>();
private readonly List<WeakReference<StreamingSound>> streamingSounds = new List<WeakReference<StreamingSound>>(); private readonly List<StreamingSound> streamingSounds = new List<StreamingSound>();
private AudioTweenPool AudioTweenPool = new AudioTweenPool(); private AudioTweenPool AudioTweenPool = new AudioTweenPool();
private readonly List<AudioTween> AudioTweens = new List<AudioTween>(); private readonly List<AudioTween> AudioTweens = new List<AudioTween>();
@ -39,7 +39,7 @@ namespace MoonWorks.Audio
private long previousTickTime; private long previousTickTime;
private Thread Thread; private Thread Thread;
private AutoResetEvent WakeSignal; private AutoResetEvent WakeSignal;
private readonly object StateLock = new object(); internal readonly object StateLock = new object();
private bool IsDisposed; private bool IsDisposed;
@ -152,15 +152,7 @@ namespace MoonWorks.Audio
for (var i = streamingSounds.Count - 1; i >= 0; i--) for (var i = streamingSounds.Count - 1; i >= 0; i--)
{ {
var weakReference = streamingSounds[i]; streamingSounds[i].Update();
if (weakReference.TryGetTarget(out var streamingSound))
{
streamingSound.Update();
}
else
{
streamingSounds.RemoveAt(i);
}
} }
for (var i = AudioTweens.Count - 1; i >= 0; i--) for (var i = AudioTweens.Count - 1; i >= 0; i--)
@ -262,7 +254,12 @@ namespace MoonWorks.Audio
private void AddDynamicSoundInstance(StreamingSound instance) private void AddDynamicSoundInstance(StreamingSound instance)
{ {
streamingSounds.Add(new WeakReference<StreamingSound>(instance)); streamingSounds.Add(instance);
}
private void RemoveDynamicSoundInstance(StreamingSound reference)
{
streamingSounds.Remove(reference);
} }
internal void AddResourceReference(AudioResource resource, WeakReference<AudioResource> resourceReference) internal void AddResourceReference(AudioResource resource, WeakReference<AudioResource> resourceReference)
@ -278,11 +275,16 @@ namespace MoonWorks.Audio
} }
} }
internal void RemoveResourceReference(WeakReference<AudioResource> resourceReference) internal void RemoveResourceReference(AudioResource resource, WeakReference<AudioResource> resourceReference)
{ {
lock (StateLock) lock (StateLock)
{ {
resources.Remove(resourceReference); 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() ~AudioDevice()
{ {
// Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method

View File

@ -28,7 +28,7 @@ namespace MoonWorks.Audio
if (selfReference != null) if (selfReference != null)
{ {
Device.RemoveResourceReference(selfReference); Device.RemoveResourceReference(this, selfReference);
selfReference = null; selfReference = null;
} }

View File

@ -92,12 +92,15 @@ namespace MoonWorks.Audio
{ {
lock (StateLock) 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) lock (StateLock)
{ {
StopImmediate(); if (!IsDisposed)
for (int i = 0; i < BUFFER_COUNT; i += 1)
{ {
NativeMemory.Free((void*) buffers[i]); StopImmediate();
for (int i = 0; i < BUFFER_COUNT; i += 1)
{
NativeMemory.Free((void*) buffers[i]);
}
} }
} }
} }

View File

@ -89,8 +89,13 @@ namespace MoonWorks.Audio
protected unsafe override void Destroy() protected unsafe override void Destroy()
{ {
FAudio.stb_vorbis_close(VorbisHandle); base.Destroy();
NativeMemory.Free((void*) FileDataPtr);
if (!IsDisposed)
{
FAudio.stb_vorbis_close(VorbisHandle);
NativeMemory.Free((void*) FileDataPtr);
}
} }
} }
} }

View File

@ -32,14 +32,20 @@ namespace MoonWorks.Video
) { ) {
var lengthInFloats = bufferLengthInBytes / sizeof(float); var lengthInFloats = bufferLengthInBytes / sizeof(float);
int samples = Theorafile.tf_readaudio( // FIXME: this gets gnarly with theorafile being not thread safe
VideoHandle, // is there some way we could just manually update in VideoPlayer
(IntPtr) buffer, // instead of going through AudioDevice?
lengthInFloats lock (Device.StateLock)
); {
int samples = Theorafile.tf_readaudio(
VideoHandle,
(IntPtr) buffer,
lengthInFloats
);
filledLengthInBytes = samples * sizeof(float); filledLengthInBytes = samples * sizeof(float);
reachedEnd = Theorafile.tf_eos(VideoHandle) == 1; reachedEnd = Theorafile.tf_eos(VideoHandle) == 1;
}
} }
protected override void OnReachedEnd() { } protected override void OnReachedEnd() { }

View File

@ -27,7 +27,7 @@ namespace MoonWorks.Video
private int yWidth; private int yWidth;
private int yHeight; private int yHeight;
private bool disposed; private bool IsDisposed;
public Video(string filename) public Video(string filename)
{ {
@ -89,7 +89,7 @@ namespace MoonWorks.Video
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!disposed) if (!IsDisposed)
{ {
if (disposing) if (disposing)
{ {
@ -100,7 +100,7 @@ namespace MoonWorks.Video
Theorafile.tf_close(ref Handle); Theorafile.tf_close(ref Handle);
NativeMemory.Free(videoData); NativeMemory.Free(videoData);
disposed = true; IsDisposed = true;
} }
} }

View File

@ -181,18 +181,22 @@ namespace MoonWorks.Video
lastTimestamp = 0; lastTimestamp = 0;
timeElapsed = 0; timeElapsed = 0;
if (audioStream != null) lock (AudioDevice.StateLock)
{ {
audioStream.StopImmediate(); DestroyAudioStream();
audioStream.Dispose();
audioStream = null; Theorafile.tf_reset(Video.Handle);
} }
Theorafile.tf_reset(Video.Handle);
State = VideoState.Stopped; State = VideoState.Stopped;
} }
public void Unload()
{
Stop();
Video = null;
}
public void Render() public void Render()
{ {
if (Video == null || State == VideoState.Stopped) if (Video == null || State == VideoState.Stopped)
@ -200,48 +204,48 @@ namespace MoonWorks.Video
return; return;
} }
timeElapsed += (timer.Elapsed.TotalMilliseconds - lastTimestamp) * PlaybackSpeed; // Theorafile is not thread safe so we have to do this. Fun!
lastTimestamp = timer.Elapsed.TotalMilliseconds; lock (AudioDevice.StateLock)
int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
if (thisFrame > currentFrame)
{ {
if (Theorafile.tf_readvideo( timeElapsed += (timer.Elapsed.TotalMilliseconds - lastTimestamp) * PlaybackSpeed;
Video.Handle, lastTimestamp = timer.Elapsed.TotalMilliseconds;
(IntPtr) yuvData,
thisFrame - currentFrame int thisFrame = ((int) (timeElapsed / (1000.0 / Video.FramesPerSecond)));
) == 1 || currentFrame == -1) { if (thisFrame > currentFrame)
UpdateRenderTexture(); {
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)
bool ended = Theorafile.tf_eos(Video.Handle) == 1;
if (ended)
{
timer.Stop();
timer.Reset();
if (audioStream != null)
{ {
audioStream.Stop(); timer.Stop();
audioStream.Dispose(); timer.Reset();
audioStream = null;
}
Theorafile.tf_reset(Video.Handle); DestroyAudioStream();
if (Loop) Theorafile.tf_reset(Video.Handle);
{
// Start over!
InitializeTheoraStream();
timer.Start(); if (Loop)
} {
else // Start over!
{ InitializeTheoraStream();
State = VideoState.Stopped;
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. // Grab the first bit of audio. We're trying to start the decoding ASAP.
if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1) if (AudioDevice != null && Theorafile.tf_hasaudio(Video.Handle) == 1)
{ {
DestroyAudioStream();
int channels, sampleRate; int channels, sampleRate;
Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate); Theorafile.tf_audioinfo(Video.Handle, out channels, out sampleRate);
audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate); audioStream = new StreamingSoundTheora(AudioDevice, Video.Handle, channels, (uint) sampleRate);
} }
currentFrame = -1; currentFrame = -1;
} }
private void DestroyAudioStream()
{
if (audioStream != null)
{
audioStream.StopImmediate();
audioStream.Dispose();
audioStream = null;
}
}
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (!disposed) if (!disposed)