/ src / Ryujinx.Audio.Backends.SDL2 / SDL2HardwareDeviceSession.cs
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  }