Haydn: Make SoundIO session implementation lock-free (#2068)

* Haydn: Fix race condition in SoundIO Update implementation

This should fix weird crashes happening for some people with SoundIO.

Fix #2062

* haydn: Make SoundIO session lock-free
This commit is contained in:
Mary 2021-02-28 17:20:34 +01:00 committed by GitHub
parent 460a98390e
commit d02eeed9c1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 28 additions and 40 deletions

View file

@ -2,8 +2,15 @@
{ {
class SoundIoAudioBuffer class SoundIoAudioBuffer
{ {
public ulong DriverIdentifier; public readonly ulong DriverIdentifier;
public ulong SampleCount; public readonly ulong SampleCount;
public ulong SamplePlayed; public ulong SamplePlayed;
public SoundIoAudioBuffer(ulong driverIdentifier, ulong sampleCount)
{
DriverIdentifier = driverIdentifier;
SampleCount = sampleCount;
SamplePlayed = 0;
}
} }
} }

View file

@ -3,7 +3,7 @@ using Ryujinx.Audio.Common;
using Ryujinx.Memory; using Ryujinx.Memory;
using SoundIOSharp; using SoundIOSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Concurrent;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
@ -11,10 +11,8 @@ namespace Ryujinx.Audio.Backends.SoundIo
{ {
class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
{ {
private object _lock = new object();
private SoundIoHardwareDeviceDriver _driver; private SoundIoHardwareDeviceDriver _driver;
private Queue<SoundIoAudioBuffer> _queuedBuffers; private ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers;
private SoundIOOutStream _outputStream; private SoundIOOutStream _outputStream;
private DynamicRingBuffer _ringBuffer; private DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount; private ulong _playedSampleCount;
@ -24,7 +22,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
{ {
_driver = driver; _driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent(); _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
_queuedBuffers = new Queue<SoundIoAudioBuffer>(); _queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
_ringBuffer = new DynamicRingBuffer(); _ringBuffer = new DynamicRingBuffer();
SetupOutputStream(); SetupOutputStream();
@ -42,10 +40,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override ulong GetPlayedSampleCount() public override ulong GetPlayedSampleCount()
{ {
lock (_lock) return Interlocked.Read(ref _playedSampleCount);
{
return _playedSampleCount;
}
} }
public override float GetVolume() public override float GetVolume()
@ -57,19 +52,11 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override void QueueBuffer(AudioBuffer buffer) public override void QueueBuffer(AudioBuffer buffer)
{ {
lock (_lock) SoundIoAudioBuffer driverBuffer = new SoundIoAudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
{
SoundIoAudioBuffer driverBuffer = new SoundIoAudioBuffer
{
DriverIdentifier = buffer.DataPointer,
SampleCount = GetSampleCount(buffer),
SamplePlayed = 0,
};
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
_queuedBuffers.Enqueue(driverBuffer); _queuedBuffers.Enqueue(driverBuffer);
}
} }
public override void SetVolume(float volume) public override void SetVolume(float volume)
@ -96,15 +83,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override bool WasBufferFullyConsumed(AudioBuffer buffer) public override bool WasBufferFullyConsumed(AudioBuffer buffer)
{ {
lock (_lock) if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer))
{ {
if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) return true;
{
return true;
}
return driverBuffer.DriverIdentifier != buffer.DataPointer;
} }
return driverBuffer.DriverIdentifier != buffer.DataPointer;
} }
private unsafe void Update(int minFrameCount, int maxFrameCount) private unsafe void Update(int minFrameCount, int maxFrameCount)
@ -413,20 +397,20 @@ namespace Ryujinx.Audio.Backends.SoundIo
while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer))
{ {
ulong sampleStillNeeded = driverBuffer.SampleCount - driverBuffer.SamplePlayed; ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
driverBuffer.SamplePlayed += playedAudioBufferSampleCount; Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount);
availaibleSampleCount -= playedAudioBufferSampleCount; availaibleSampleCount -= playedAudioBufferSampleCount;
if (driverBuffer.SamplePlayed == driverBuffer.SampleCount) if (Interlocked.Read(ref driverBuffer.SamplePlayed) == driverBuffer.SampleCount)
{ {
_queuedBuffers.TryDequeue(out _); _queuedBuffers.TryDequeue(out _);
needUpdate = true; needUpdate = true;
} }
_playedSampleCount += playedAudioBufferSampleCount; Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount);
} }
// Notify the output if needed. // Notify the output if needed.
@ -440,15 +424,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
{ {
if (disposing) if (disposing)
{ {
lock (_lock) PrepareToClose();
{ Stop();
PrepareToClose();
Stop();
_outputStream.Dispose(); _outputStream.Dispose();
_driver.Unregister(this); _driver.Unregister(this);
}
} }
} }