CompatLayerHardwareDeviceDriver.cs
1 using Ryujinx.Audio.Backends.Common; 2 using Ryujinx.Audio.Backends.Dummy; 3 using Ryujinx.Audio.Common; 4 using Ryujinx.Audio.Integration; 5 using Ryujinx.Common.Logging; 6 using Ryujinx.Memory; 7 using System; 8 using System.Threading; 9 using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; 10 11 namespace Ryujinx.Audio.Backends.CompatLayer 12 { 13 public class CompatLayerHardwareDeviceDriver : IHardwareDeviceDriver 14 { 15 private readonly IHardwareDeviceDriver _realDriver; 16 17 public static bool IsSupported => true; 18 19 public float Volume 20 { 21 get => _realDriver.Volume; 22 set => _realDriver.Volume = value; 23 } 24 25 public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice) 26 { 27 _realDriver = realDevice; 28 } 29 30 public void Dispose() 31 { 32 GC.SuppressFinalize(this); 33 _realDriver.Dispose(); 34 } 35 36 public ManualResetEvent GetUpdateRequiredEvent() 37 { 38 return _realDriver.GetUpdateRequiredEvent(); 39 } 40 41 public ManualResetEvent GetPauseEvent() 42 { 43 return _realDriver.GetPauseEvent(); 44 } 45 46 private uint SelectHardwareChannelCount(uint targetChannelCount) 47 { 48 if (_realDriver.SupportsChannelCount(targetChannelCount)) 49 { 50 return targetChannelCount; 51 } 52 53 return targetChannelCount switch 54 { 55 6 => SelectHardwareChannelCount(2), 56 2 => SelectHardwareChannelCount(1), 57 1 => throw new ArgumentException("No valid channel configuration found!"), 58 _ => throw new ArgumentException($"Invalid targetChannelCount {targetChannelCount}"), 59 }; 60 } 61 62 private SampleFormat SelectHardwareSampleFormat(SampleFormat targetSampleFormat) 63 { 64 if (_realDriver.SupportsSampleFormat(targetSampleFormat)) 65 { 66 return targetSampleFormat; 67 } 68 69 // Attempt conversion from PCM16. 70 if (targetSampleFormat == SampleFormat.PcmInt16) 71 { 72 // Prefer PCM32 if we need to convert. 73 if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt32)) 74 { 75 return SampleFormat.PcmInt32; 76 } 77 78 // If not supported, PCM float provides the best quality with a cost lower than PCM24. 79 if (_realDriver.SupportsSampleFormat(SampleFormat.PcmFloat)) 80 { 81 return SampleFormat.PcmFloat; 82 } 83 84 if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt24)) 85 { 86 return SampleFormat.PcmInt24; 87 } 88 89 // If nothing is truly supported, attempt PCM8 at the cost of losing quality. 90 if (_realDriver.SupportsSampleFormat(SampleFormat.PcmInt8)) 91 { 92 return SampleFormat.PcmInt8; 93 } 94 } 95 96 throw new ArgumentException("No valid sample format configuration found!"); 97 } 98 99 public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) 100 { 101 if (channelCount == 0) 102 { 103 channelCount = 2; 104 } 105 106 if (sampleRate == 0) 107 { 108 sampleRate = Constants.TargetSampleRate; 109 } 110 111 if (!_realDriver.SupportsDirection(direction)) 112 { 113 if (direction == Direction.Input) 114 { 115 Logger.Warning?.Print(LogClass.Audio, "The selected audio backend doesn't support audio input, fallback to dummy..."); 116 117 return new DummyHardwareDeviceSessionInput(this, memoryManager); 118 } 119 120 throw new NotImplementedException(); 121 } 122 123 SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat); 124 uint hardwareChannelCount = SelectHardwareChannelCount(channelCount); 125 126 IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount); 127 128 if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat) 129 { 130 return realSession; 131 } 132 133 if (hardwareSampleFormat != sampleFormat) 134 { 135 Logger.Warning?.Print(LogClass.Audio, $"{sampleFormat} isn't supported by the audio device, conversion to {hardwareSampleFormat} will happen."); 136 137 if (hardwareSampleFormat < sampleFormat) 138 { 139 Logger.Warning?.Print(LogClass.Audio, $"{hardwareSampleFormat} has lower quality than {sampleFormat}, expect some loss in audio fidelity."); 140 } 141 } 142 143 if (direction == Direction.Input) 144 { 145 Logger.Warning?.Print(LogClass.Audio, "The selected audio backend doesn't support the requested audio input configuration, fallback to dummy..."); 146 147 // TODO: We currently don't support audio input upsampling/downsampling, implement this. 148 realSession.Dispose(); 149 150 return new DummyHardwareDeviceSessionInput(this, memoryManager); 151 } 152 153 // It must be a HardwareDeviceSessionOutputBase. 154 if (realSession is not HardwareDeviceSessionOutputBase realSessionOutputBase) 155 { 156 throw new InvalidOperationException($"Real driver session class type isn't based on {typeof(HardwareDeviceSessionOutputBase).Name}."); 157 } 158 159 // If we need to do post processing before sending to the hardware device, wrap around it. 160 return new CompatLayerHardwareDeviceSession(realSessionOutputBase, sampleFormat, channelCount); 161 } 162 163 public bool SupportsChannelCount(uint channelCount) 164 { 165 return channelCount == 1 || channelCount == 2 || channelCount == 6; 166 } 167 168 public bool SupportsSampleFormat(SampleFormat sampleFormat) 169 { 170 // TODO: More formats. 171 return sampleFormat == SampleFormat.PcmInt16; 172 } 173 174 public bool SupportsSampleRate(uint sampleRate) 175 { 176 // TODO: More sample rates. 177 return sampleRate == Constants.TargetSampleRate; 178 } 179 180 public IHardwareDeviceDriver GetRealDeviceDriver() 181 { 182 return _realDriver; 183 } 184 185 public bool SupportsDirection(Direction direction) 186 { 187 return direction == Direction.Input || direction == Direction.Output; 188 } 189 } 190 }