/ src / Ryujinx.Audio.Backends.SoundIo / SoundIoHardwareDeviceSession.cs
SoundIoHardwareDeviceSession.cs
  1  using Ryujinx.Audio.Backends.Common;
  2  using Ryujinx.Audio.Backends.SoundIo.Native;
  3  using Ryujinx.Audio.Common;
  4  using Ryujinx.Common.Memory;
  5  using Ryujinx.Memory;
  6  using System;
  7  using System.Buffers;
  8  using System.Collections.Concurrent;
  9  using System.Runtime.CompilerServices;
 10  using System.Threading;
 11  using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
 12  
 13  namespace Ryujinx.Audio.Backends.SoundIo
 14  {
 15      class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
 16      {
 17          private readonly SoundIoHardwareDeviceDriver _driver;
 18          private readonly ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers;
 19          private SoundIoOutStreamContext _outputStream;
 20          private readonly DynamicRingBuffer _ringBuffer;
 21          private ulong _playedSampleCount;
 22          private readonly ManualResetEvent _updateRequiredEvent;
 23          private float _volume;
 24          private int _disposeState;
 25  
 26          public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
 27          {
 28              _driver = driver;
 29              _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
 30              _queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
 31              _ringBuffer = new DynamicRingBuffer();
 32              _volume = 1f;
 33  
 34              SetupOutputStream(driver.Volume);
 35          }
 36  
 37          private void SetupOutputStream(float requestedVolume)
 38          {
 39              _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
 40              _outputStream.WriteCallback += Update;
 41              _outputStream.Volume = requestedVolume;
 42              // TODO: Setup other callbacks (errors, etc.)
 43  
 44              _outputStream.Open();
 45          }
 46  
 47          public override ulong GetPlayedSampleCount()
 48          {
 49              return Interlocked.Read(ref _playedSampleCount);
 50          }
 51  
 52          public override float GetVolume()
 53          {
 54              return _volume;
 55          }
 56  
 57          public override void PrepareToClose() { }
 58  
 59          public override void QueueBuffer(AudioBuffer buffer)
 60          {
 61              SoundIoAudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
 62  
 63              _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
 64  
 65              _queuedBuffers.Enqueue(driverBuffer);
 66          }
 67  
 68          public override void SetVolume(float volume)
 69          {
 70              _volume = volume;
 71  
 72              _outputStream.SetVolume(_driver.Volume * volume);
 73          }
 74  
 75          public void UpdateMasterVolume(float newVolume)
 76          {
 77              _outputStream.SetVolume(newVolume * _volume);
 78          }
 79  
 80          public override void Start()
 81          {
 82              _outputStream.Start();
 83              _outputStream.Pause(false);
 84  
 85              _driver.FlushContextEvents();
 86          }
 87  
 88          public override void Stop()
 89          {
 90              _outputStream.Pause(true);
 91  
 92              _driver.FlushContextEvents();
 93          }
 94  
 95          public override void UnregisterBuffer(AudioBuffer buffer) { }
 96  
 97          public override bool WasBufferFullyConsumed(AudioBuffer buffer)
 98          {
 99              if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer))
100              {
101                  return true;
102              }
103  
104              return driverBuffer.DriverIdentifier != buffer.DataPointer;
105          }
106  
107          private unsafe void Update(int minFrameCount, int maxFrameCount)
108          {
109              int bytesPerFrame = _outputStream.BytesPerFrame;
110              uint bytesPerSample = (uint)_outputStream.BytesPerSample;
111  
112              int bufferedFrames = _ringBuffer.Length / bytesPerFrame;
113  
114              int frameCount = Math.Min(bufferedFrames, maxFrameCount);
115  
116              if (frameCount == 0)
117              {
118                  return;
119              }
120  
121              Span<SoundIoChannelArea> areas = _outputStream.BeginWrite(ref frameCount);
122  
123              int channelCount = areas.Length;
124  
125              using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * bytesPerFrame);
126  
127              Span<byte> samples = samplesOwner.Span;
128  
129              _ringBuffer.Read(samples, 0, samples.Length);
130  
131              // This is a huge ugly block of code, but we save
132              // a significant amount of time over the generic
133              // loop that handles other channel counts.
134              // TODO: Is this still right in 2022?
135  
136              // Mono
137              if (channelCount == 1)
138              {
139                  ref SoundIoChannelArea area = ref areas[0];
140  
141                  fixed (byte* srcptr = samples)
142                  {
143                      if (bytesPerSample == 1)
144                      {
145                          for (int frame = 0; frame < frameCount; frame++)
146                          {
147                              ((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame];
148  
149                              area.Pointer += area.Step;
150                          }
151                      }
152                      else if (bytesPerSample == 2)
153                      {
154                          for (int frame = 0; frame < frameCount; frame++)
155                          {
156                              ((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1];
157  
158                              area.Pointer += area.Step;
159                          }
160                      }
161                      else if (bytesPerSample == 4)
162                      {
163                          for (int frame = 0; frame < frameCount; frame++)
164                          {
165                              ((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2];
166  
167                              area.Pointer += area.Step;
168                          }
169                      }
170                      else
171                      {
172                          for (int frame = 0; frame < frameCount; frame++)
173                          {
174                              Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample);
175  
176                              area.Pointer += area.Step;
177                          }
178                      }
179                  }
180              }
181              // Stereo
182              else if (channelCount == 2)
183              {
184                  ref SoundIoChannelArea area1 = ref areas[0];
185                  ref SoundIoChannelArea area2 = ref areas[1];
186  
187                  fixed (byte* srcptr = samples)
188                  {
189                      if (bytesPerSample == 1)
190                      {
191                          for (int frame = 0; frame < frameCount; frame++)
192                          {
193                              // Channel 1
194                              ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0];
195  
196                              // Channel 2
197                              ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1];
198  
199                              area1.Pointer += area1.Step;
200                              area2.Pointer += area2.Step;
201                          }
202                      }
203                      else if (bytesPerSample == 2)
204                      {
205                          for (int frame = 0; frame < frameCount; frame++)
206                          {
207                              // Channel 1
208                              ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0];
209  
210                              // Channel 2
211                              ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1];
212  
213                              area1.Pointer += area1.Step;
214                              area2.Pointer += area2.Step;
215                          }
216                      }
217                      else if (bytesPerSample == 4)
218                      {
219                          for (int frame = 0; frame < frameCount; frame++)
220                          {
221                              // Channel 1
222                              ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0];
223  
224                              // Channel 2
225                              ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1];
226  
227                              area1.Pointer += area1.Step;
228                              area2.Pointer += area2.Step;
229                          }
230                      }
231                      else
232                      {
233                          for (int frame = 0; frame < frameCount; frame++)
234                          {
235                              // Channel 1
236                              Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample);
237  
238                              // Channel 2
239                              Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample);
240  
241                              area1.Pointer += area1.Step;
242                              area2.Pointer += area2.Step;
243                          }
244                      }
245                  }
246              }
247              // Surround
248              else if (channelCount == 6)
249              {
250                  ref SoundIoChannelArea area1 = ref areas[0];
251                  ref SoundIoChannelArea area2 = ref areas[1];
252                  ref SoundIoChannelArea area3 = ref areas[2];
253                  ref SoundIoChannelArea area4 = ref areas[3];
254                  ref SoundIoChannelArea area5 = ref areas[4];
255                  ref SoundIoChannelArea area6 = ref areas[5];
256  
257                  fixed (byte* srcptr = samples)
258                  {
259                      if (bytesPerSample == 1)
260                      {
261                          for (int frame = 0; frame < frameCount; frame++)
262                          {
263                              // Channel 1
264                              ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0];
265  
266                              // Channel 2
267                              ((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1];
268  
269                              // Channel 3
270                              ((byte*)area3.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 2];
271  
272                              // Channel 4
273                              ((byte*)area4.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 3];
274  
275                              // Channel 5
276                              ((byte*)area5.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 4];
277  
278                              // Channel 6
279                              ((byte*)area6.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 5];
280  
281                              area1.Pointer += area1.Step;
282                              area2.Pointer += area2.Step;
283                              area3.Pointer += area3.Step;
284                              area4.Pointer += area4.Step;
285                              area5.Pointer += area5.Step;
286                              area6.Pointer += area6.Step;
287                          }
288                      }
289                      else if (bytesPerSample == 2)
290                      {
291                          for (int frame = 0; frame < frameCount; frame++)
292                          {
293                              // Channel 1
294                              ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0];
295  
296                              // Channel 2
297                              ((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1];
298  
299                              // Channel 3
300                              ((short*)area3.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 2];
301  
302                              // Channel 4
303                              ((short*)area4.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 3];
304  
305                              // Channel 5
306                              ((short*)area5.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 4];
307  
308                              // Channel 6
309                              ((short*)area6.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 5];
310  
311                              area1.Pointer += area1.Step;
312                              area2.Pointer += area2.Step;
313                              area3.Pointer += area3.Step;
314                              area4.Pointer += area4.Step;
315                              area5.Pointer += area5.Step;
316                              area6.Pointer += area6.Step;
317                          }
318                      }
319                      else if (bytesPerSample == 4)
320                      {
321                          for (int frame = 0; frame < frameCount; frame++)
322                          {
323                              // Channel 1
324                              ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0];
325  
326                              // Channel 2
327                              ((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1];
328  
329                              // Channel 3
330                              ((int*)area3.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 2];
331  
332                              // Channel 4
333                              ((int*)area4.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 3];
334  
335                              // Channel 5
336                              ((int*)area5.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 4];
337  
338                              // Channel 6
339                              ((int*)area6.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 5];
340  
341                              area1.Pointer += area1.Step;
342                              area2.Pointer += area2.Step;
343                              area3.Pointer += area3.Step;
344                              area4.Pointer += area4.Step;
345                              area5.Pointer += area5.Step;
346                              area6.Pointer += area6.Step;
347                          }
348                      }
349                      else
350                      {
351                          for (int frame = 0; frame < frameCount; frame++)
352                          {
353                              // Channel 1
354                              Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample);
355  
356                              // Channel 2
357                              Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample);
358  
359                              // Channel 3
360                              Unsafe.CopyBlockUnaligned((byte*)area3.Pointer, srcptr + (frame * bytesPerFrame) + (2 * bytesPerSample), bytesPerSample);
361  
362                              // Channel 4
363                              Unsafe.CopyBlockUnaligned((byte*)area4.Pointer, srcptr + (frame * bytesPerFrame) + (3 * bytesPerSample), bytesPerSample);
364  
365                              // Channel 5
366                              Unsafe.CopyBlockUnaligned((byte*)area5.Pointer, srcptr + (frame * bytesPerFrame) + (4 * bytesPerSample), bytesPerSample);
367  
368                              // Channel 6
369                              Unsafe.CopyBlockUnaligned((byte*)area6.Pointer, srcptr + (frame * bytesPerFrame) + (5 * bytesPerSample), bytesPerSample);
370  
371                              area1.Pointer += area1.Step;
372                              area2.Pointer += area2.Step;
373                              area3.Pointer += area3.Step;
374                              area4.Pointer += area4.Step;
375                              area5.Pointer += area5.Step;
376                              area6.Pointer += area6.Step;
377                          }
378                      }
379                  }
380              }
381              // Every other channel count
382              else
383              {
384                  fixed (byte* srcptr = samples)
385                  {
386                      for (int frame = 0; frame < frameCount; frame++)
387                      {
388                          for (int channel = 0; channel < areas.Length; channel++)
389                          {
390                              // Copy channel by channel, frame by frame. This is slow!
391                              Unsafe.CopyBlockUnaligned((byte*)areas[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample);
392  
393                              areas[channel].Pointer += areas[channel].Step;
394                          }
395                      }
396                  }
397              }
398  
399              _outputStream.EndWrite();
400  
401              ulong sampleCount = (ulong)(samples.Length / bytesPerSample / channelCount);
402  
403              ulong availaibleSampleCount = sampleCount;
404  
405              bool needUpdate = false;
406  
407              while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer))
408              {
409                  ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
410                  ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
411  
412                  Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount);
413                  availaibleSampleCount -= playedAudioBufferSampleCount;
414  
415                  if (Interlocked.Read(ref driverBuffer.SamplePlayed) == driverBuffer.SampleCount)
416                  {
417                      _queuedBuffers.TryDequeue(out _);
418  
419                      needUpdate = true;
420                  }
421  
422                  Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount);
423              }
424  
425              // Notify the output if needed.
426              if (needUpdate)
427              {
428                  _updateRequiredEvent.Set();
429              }
430          }
431  
432          protected virtual void Dispose(bool disposing)
433          {
434              if (disposing && _driver.Unregister(this))
435              {
436                  PrepareToClose();
437                  Stop();
438  
439                  _outputStream.Dispose();
440              }
441          }
442  
443          public override void Dispose()
444          {
445              if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
446              {
447                  Dispose(true);
448              }
449          }
450      }
451  }