SoundIoHardwareDeviceDriver.cs
1 using Ryujinx.Audio.Backends.SoundIo.Native; 2 using Ryujinx.Audio.Common; 3 using Ryujinx.Audio.Integration; 4 using Ryujinx.Memory; 5 using System; 6 using System.Collections.Concurrent; 7 using System.Threading; 8 using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo; 9 using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; 10 11 namespace Ryujinx.Audio.Backends.SoundIo 12 { 13 public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver 14 { 15 private readonly SoundIoContext _audioContext; 16 private readonly SoundIoDeviceContext _audioDevice; 17 private readonly ManualResetEvent _updateRequiredEvent; 18 private readonly ManualResetEvent _pauseEvent; 19 private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions; 20 private int _disposeState; 21 22 private float _volume = 1f; 23 24 public float Volume 25 { 26 get 27 { 28 return _volume; 29 } 30 set 31 { 32 _volume = value; 33 34 foreach (SoundIoHardwareDeviceSession session in _sessions.Keys) 35 { 36 session.UpdateMasterVolume(value); 37 } 38 } 39 } 40 41 public SoundIoHardwareDeviceDriver() 42 { 43 _audioContext = SoundIoContext.Create(); 44 _updateRequiredEvent = new ManualResetEvent(false); 45 _pauseEvent = new ManualResetEvent(true); 46 _sessions = new ConcurrentDictionary<SoundIoHardwareDeviceSession, byte>(); 47 48 _audioContext.Connect(); 49 _audioContext.FlushEvents(); 50 51 _audioDevice = FindValidAudioDevice(_audioContext, true); 52 } 53 54 public static bool IsSupported => IsSupportedInternal(); 55 56 private static bool IsSupportedInternal() 57 { 58 SoundIoContext context = null; 59 SoundIoDeviceContext device = null; 60 SoundIoOutStreamContext stream = null; 61 62 bool backendDisconnected = false; 63 64 try 65 { 66 context = SoundIoContext.Create(); 67 context.OnBackendDisconnect = err => 68 { 69 backendDisconnected = true; 70 }; 71 72 context.Connect(); 73 context.FlushEvents(); 74 75 if (backendDisconnected) 76 { 77 return false; 78 } 79 80 if (context.OutputDeviceCount == 0) 81 { 82 return false; 83 } 84 85 device = FindValidAudioDevice(context); 86 87 if (device == null || backendDisconnected) 88 { 89 return false; 90 } 91 92 stream = device.CreateOutStream(); 93 94 if (stream == null || backendDisconnected) 95 { 96 return false; 97 } 98 99 return true; 100 } 101 catch 102 { 103 return false; 104 } 105 finally 106 { 107 stream?.Dispose(); 108 context?.Dispose(); 109 } 110 } 111 112 private static SoundIoDeviceContext FindValidAudioDevice(SoundIoContext audioContext, bool fallback = false) 113 { 114 SoundIoDeviceContext defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex); 115 116 if (!defaultAudioDevice.IsRaw) 117 { 118 return defaultAudioDevice; 119 } 120 121 for (int i = 0; i < audioContext.OutputDeviceCount; i++) 122 { 123 SoundIoDeviceContext audioDevice = audioContext.GetOutputDevice(i); 124 125 if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw) 126 { 127 return audioDevice; 128 } 129 } 130 131 return fallback ? defaultAudioDevice : null; 132 } 133 134 public ManualResetEvent GetUpdateRequiredEvent() 135 { 136 return _updateRequiredEvent; 137 } 138 139 public ManualResetEvent GetPauseEvent() 140 { 141 return _pauseEvent; 142 } 143 144 public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) 145 { 146 if (channelCount == 0) 147 { 148 channelCount = 2; 149 } 150 151 if (sampleRate == 0) 152 { 153 sampleRate = Constants.TargetSampleRate; 154 } 155 156 if (direction != Direction.Output) 157 { 158 throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!"); 159 } 160 161 SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount); 162 163 _sessions.TryAdd(session, 0); 164 165 return session; 166 } 167 168 internal bool Unregister(SoundIoHardwareDeviceSession session) 169 { 170 return _sessions.TryRemove(session, out _); 171 } 172 173 public static SoundIoFormat GetSoundIoFormat(SampleFormat format) 174 { 175 return format switch 176 { 177 SampleFormat.PcmInt8 => SoundIoFormat.S8, 178 SampleFormat.PcmInt16 => SoundIoFormat.S16LE, 179 SampleFormat.PcmInt24 => SoundIoFormat.S24LE, 180 SampleFormat.PcmInt32 => SoundIoFormat.S32LE, 181 SampleFormat.PcmFloat => SoundIoFormat.Float32LE, 182 _ => throw new ArgumentException($"Unsupported sample format {format}"), 183 }; 184 } 185 186 internal SoundIoOutStreamContext OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) 187 { 188 SoundIoFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat); 189 190 if (!_audioDevice.SupportsSampleRate((int)requestedSampleRate)) 191 { 192 throw new ArgumentException($"This sound device does not support a sample rate of {requestedSampleRate}Hz"); 193 } 194 195 if (!_audioDevice.SupportsFormat(driverSampleFormat)) 196 { 197 throw new ArgumentException($"This sound device does not support {requestedSampleFormat}"); 198 } 199 200 if (!_audioDevice.SupportsChannelCount((int)requestedChannelCount)) 201 { 202 throw new ArgumentException($"This sound device does not support channel count {requestedChannelCount}"); 203 } 204 205 SoundIoOutStreamContext result = _audioDevice.CreateOutStream(); 206 207 result.Name = "Ryujinx"; 208 result.Layout = SoundIoChannelLayout.GetDefaultValue((int)requestedChannelCount); 209 result.Format = driverSampleFormat; 210 result.SampleRate = (int)requestedSampleRate; 211 212 return result; 213 } 214 215 internal void FlushContextEvents() 216 { 217 _audioContext.FlushEvents(); 218 } 219 220 public void Dispose() 221 { 222 GC.SuppressFinalize(this); 223 224 if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0) 225 { 226 Dispose(true); 227 } 228 } 229 230 protected virtual void Dispose(bool disposing) 231 { 232 if (disposing) 233 { 234 foreach (SoundIoHardwareDeviceSession session in _sessions.Keys) 235 { 236 session.Dispose(); 237 } 238 239 _audioContext.Disconnect(); 240 _audioContext.Dispose(); 241 _pauseEvent.Dispose(); 242 } 243 } 244 245 public bool SupportsSampleRate(uint sampleRate) 246 { 247 return _audioDevice.SupportsSampleRate((int)sampleRate); 248 } 249 250 public bool SupportsSampleFormat(SampleFormat sampleFormat) 251 { 252 return _audioDevice.SupportsFormat(GetSoundIoFormat(sampleFormat)); 253 } 254 255 public bool SupportsChannelCount(uint channelCount) 256 { 257 return _audioDevice.SupportsChannelCount((int)channelCount); 258 } 259 260 public bool SupportsDirection(Direction direction) 261 { 262 // TODO: add direction input when supported. 263 return direction == Direction.Output; 264 } 265 } 266 }