/ src / Ryujinx.Audio.Backends.SDL2 / SDL2HardwareDeviceDriver.cs
SDL2HardwareDeviceDriver.cs
  1  using Ryujinx.Audio.Common;
  2  using Ryujinx.Audio.Integration;
  3  using Ryujinx.Common.Logging;
  4  using Ryujinx.Memory;
  5  using Ryujinx.SDL2.Common;
  6  using System;
  7  using System.Collections.Concurrent;
  8  using System.Runtime.InteropServices;
  9  using System.Threading;
 10  using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
 11  using static SDL2.SDL;
 12  
 13  namespace Ryujinx.Audio.Backends.SDL2
 14  {
 15      public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver
 16      {
 17          private readonly ManualResetEvent _updateRequiredEvent;
 18          private readonly ManualResetEvent _pauseEvent;
 19          private readonly ConcurrentDictionary<SDL2HardwareDeviceSession, byte> _sessions;
 20  
 21          private readonly bool _supportSurroundConfiguration;
 22  
 23          public float Volume { get; set; }
 24  
 25          // TODO: Add this to SDL2-CS
 26          // NOTE: We use a DllImport here because of marshaling issue for spec.
 27  #pragma warning disable SYSLIB1054
 28          [DllImport("SDL2")]
 29          private static extern int SDL_GetDefaultAudioInfo(IntPtr name, out SDL_AudioSpec spec, int isCapture);
 30  #pragma warning restore SYSLIB1054
 31  
 32          public SDL2HardwareDeviceDriver()
 33          {
 34              _updateRequiredEvent = new ManualResetEvent(false);
 35              _pauseEvent = new ManualResetEvent(true);
 36              _sessions = new ConcurrentDictionary<SDL2HardwareDeviceSession, byte>();
 37  
 38              SDL2Driver.Instance.Initialize();
 39  
 40              int res = SDL_GetDefaultAudioInfo(IntPtr.Zero, out var spec, 0);
 41  
 42              if (res != 0)
 43              {
 44                  Logger.Error?.Print(LogClass.Application,
 45                      $"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\"");
 46  
 47                  _supportSurroundConfiguration = true;
 48              }
 49              else
 50              {
 51                  _supportSurroundConfiguration = spec.channels >= 6;
 52              }
 53  
 54              Volume = 1f;
 55          }
 56  
 57          public static bool IsSupported => IsSupportedInternal();
 58  
 59          private static bool IsSupportedInternal()
 60          {
 61              uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null);
 62  
 63              if (device != 0)
 64              {
 65                  SDL_CloseAudioDevice(device);
 66              }
 67  
 68              return device != 0;
 69          }
 70  
 71          public ManualResetEvent GetUpdateRequiredEvent()
 72          {
 73              return _updateRequiredEvent;
 74          }
 75  
 76          public ManualResetEvent GetPauseEvent()
 77          {
 78              return _pauseEvent;
 79          }
 80  
 81          public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
 82          {
 83              if (channelCount == 0)
 84              {
 85                  channelCount = 2;
 86              }
 87  
 88              if (sampleRate == 0)
 89              {
 90                  sampleRate = Constants.TargetSampleRate;
 91              }
 92  
 93              if (direction != Direction.Output)
 94              {
 95                  throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
 96              }
 97  
 98              SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
 99  
100              _sessions.TryAdd(session, 0);
101  
102              return session;
103          }
104  
105          internal bool Unregister(SDL2HardwareDeviceSession session)
106          {
107              return _sessions.TryRemove(session, out _);
108          }
109  
110          private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount)
111          {
112              return new SDL_AudioSpec
113              {
114                  channels = (byte)requestedChannelCount,
115                  format = GetSDL2Format(requestedSampleFormat),
116                  freq = (int)requestedSampleRate,
117                  samples = (ushort)sampleCount,
118              };
119          }
120  
121          internal static ushort GetSDL2Format(SampleFormat format)
122          {
123              return format switch
124              {
125                  SampleFormat.PcmInt8 => AUDIO_S8,
126                  SampleFormat.PcmInt16 => AUDIO_S16,
127                  SampleFormat.PcmInt32 => AUDIO_S32,
128                  SampleFormat.PcmFloat => AUDIO_F32,
129                  _ => throw new ArgumentException($"Unsupported sample format {format}"),
130              };
131          }
132  
133          internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback)
134          {
135              SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount);
136  
137              desired.callback = callback;
138  
139              uint device = SDL_OpenAudioDevice(IntPtr.Zero, 0, ref desired, out SDL_AudioSpec got, 0);
140  
141              if (device == 0)
142              {
143                  Logger.Error?.Print(LogClass.Application, $"SDL2 open audio device initialization failed with error \"{SDL_GetError()}\"");
144  
145                  return 0;
146              }
147  
148              bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels;
149  
150              if (!isValid)
151              {
152                  Logger.Error?.Print(LogClass.Application, "SDL2 open audio device is not valid");
153                  SDL_CloseAudioDevice(device);
154  
155                  return 0;
156              }
157  
158              return device;
159          }
160  
161          public void Dispose()
162          {
163              GC.SuppressFinalize(this);
164              Dispose(true);
165          }
166  
167          protected virtual void Dispose(bool disposing)
168          {
169              if (disposing)
170              {
171                  foreach (SDL2HardwareDeviceSession session in _sessions.Keys)
172                  {
173                      session.Dispose();
174                  }
175  
176                  SDL2Driver.Instance.Dispose();
177  
178                  _pauseEvent.Dispose();
179              }
180          }
181  
182          public bool SupportsSampleRate(uint sampleRate)
183          {
184              return true;
185          }
186  
187          public bool SupportsSampleFormat(SampleFormat sampleFormat)
188          {
189              return sampleFormat != SampleFormat.PcmInt24;
190          }
191  
192          public bool SupportsChannelCount(uint channelCount)
193          {
194              if (channelCount == 6)
195              {
196                  return _supportSurroundConfiguration;
197              }
198  
199              return true;
200          }
201  
202          public bool SupportsDirection(Direction direction)
203          {
204              // TODO: add direction input when supported.
205              return direction == Direction.Output;
206          }
207      }
208  }