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 }