SDL2HardwareDeviceSession.cs
1 using Ryujinx.Audio.Backends.Common; 2 using Ryujinx.Audio.Common; 3 using Ryujinx.Common.Logging; 4 using Ryujinx.Common.Memory; 5 using Ryujinx.Memory; 6 using System; 7 using System.Buffers; 8 using System.Collections.Concurrent; 9 using System.Threading; 10 11 using static SDL2.SDL; 12 13 namespace Ryujinx.Audio.Backends.SDL2 14 { 15 class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase 16 { 17 private readonly SDL2HardwareDeviceDriver _driver; 18 private readonly ConcurrentQueue<SDL2AudioBuffer> _queuedBuffers; 19 private readonly DynamicRingBuffer _ringBuffer; 20 private ulong _playedSampleCount; 21 private readonly ManualResetEvent _updateRequiredEvent; 22 private uint _outputStream; 23 private bool _hasSetupError; 24 private readonly SDL_AudioCallback _callbackDelegate; 25 private readonly int _bytesPerFrame; 26 private uint _sampleCount; 27 private bool _started; 28 private float _volume; 29 private readonly ushort _nativeSampleFormat; 30 31 public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) 32 { 33 _driver = driver; 34 _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); 35 _queuedBuffers = new ConcurrentQueue<SDL2AudioBuffer>(); 36 _ringBuffer = new DynamicRingBuffer(); 37 _callbackDelegate = Update; 38 _bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount; 39 _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat); 40 _sampleCount = uint.MaxValue; 41 _started = false; 42 _volume = 1f; 43 } 44 45 private void EnsureAudioStreamSetup(AudioBuffer buffer) 46 { 47 uint bufferSampleCount = (uint)GetSampleCount(buffer); 48 bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) || 49 (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount); 50 51 if (needAudioSetup) 52 { 53 _sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount); 54 55 uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); 56 57 _hasSetupError = newOutputStream == 0; 58 59 if (!_hasSetupError) 60 { 61 if (_outputStream != 0) 62 { 63 SDL_CloseAudioDevice(_outputStream); 64 } 65 66 _outputStream = newOutputStream; 67 68 SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1); 69 70 Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}"); 71 } 72 } 73 } 74 75 private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength) 76 { 77 Span<byte> streamSpan = new((void*)stream, streamLength); 78 79 int maxFrameCount = (int)GetSampleCount(streamLength); 80 int bufferedFrames = _ringBuffer.Length / _bytesPerFrame; 81 82 int frameCount = Math.Min(bufferedFrames, maxFrameCount); 83 84 if (frameCount == 0) 85 { 86 // SDL2 left the responsibility to the user to clear the buffer. 87 streamSpan.Clear(); 88 89 return; 90 } 91 92 using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame); 93 94 Span<byte> samples = samplesOwner.Span; 95 96 _ringBuffer.Read(samples, 0, samples.Length); 97 98 fixed (byte* p = samples) 99 { 100 IntPtr pStreamSrc = (IntPtr)p; 101 102 // Zero the dest buffer 103 streamSpan.Clear(); 104 105 // Apply volume to written data 106 SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME)); 107 } 108 109 ulong sampleCount = GetSampleCount(samples.Length); 110 111 ulong availaibleSampleCount = sampleCount; 112 113 bool needUpdate = false; 114 115 while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) 116 { 117 ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); 118 ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); 119 120 ulong currentSamplePlayed = Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount); 121 availaibleSampleCount -= playedAudioBufferSampleCount; 122 123 if (currentSamplePlayed == driverBuffer.SampleCount) 124 { 125 _queuedBuffers.TryDequeue(out _); 126 127 needUpdate = true; 128 } 129 130 Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount); 131 } 132 133 // Notify the output if needed. 134 if (needUpdate) 135 { 136 _updateRequiredEvent.Set(); 137 } 138 } 139 140 public override ulong GetPlayedSampleCount() 141 { 142 return Interlocked.Read(ref _playedSampleCount); 143 } 144 145 public override float GetVolume() 146 { 147 return _volume; 148 } 149 150 public override void PrepareToClose() { } 151 152 public override void QueueBuffer(AudioBuffer buffer) 153 { 154 EnsureAudioStreamSetup(buffer); 155 156 if (_outputStream != 0) 157 { 158 SDL2AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer)); 159 160 _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); 161 162 _queuedBuffers.Enqueue(driverBuffer); 163 } 164 else 165 { 166 Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); 167 168 _updateRequiredEvent.Set(); 169 } 170 } 171 172 public override void SetVolume(float volume) 173 { 174 _volume = volume; 175 } 176 177 public override void Start() 178 { 179 if (!_started) 180 { 181 if (_outputStream != 0) 182 { 183 SDL_PauseAudioDevice(_outputStream, 0); 184 } 185 186 _started = true; 187 } 188 } 189 190 public override void Stop() 191 { 192 if (_started) 193 { 194 if (_outputStream != 0) 195 { 196 SDL_PauseAudioDevice(_outputStream, 1); 197 } 198 199 _started = false; 200 } 201 } 202 203 public override void UnregisterBuffer(AudioBuffer buffer) { } 204 205 public override bool WasBufferFullyConsumed(AudioBuffer buffer) 206 { 207 if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) 208 { 209 return true; 210 } 211 212 return driverBuffer.DriverIdentifier != buffer.DataPointer; 213 } 214 215 protected virtual void Dispose(bool disposing) 216 { 217 if (disposing && _driver.Unregister(this)) 218 { 219 PrepareToClose(); 220 Stop(); 221 222 if (_outputStream != 0) 223 { 224 SDL_CloseAudioDevice(_outputStream); 225 } 226 } 227 } 228 229 public override void Dispose() 230 { 231 Dispose(true); 232 } 233 } 234 }