/ src / Ryujinx.Audio / Backends / CompatLayer / CompatLayerHardwareDeviceDriver.cs
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  }