/ src / Ryujinx.Audio / Input / AudioInputManager.cs
AudioInputManager.cs
  1  using Ryujinx.Audio.Common;
  2  using Ryujinx.Audio.Integration;
  3  using Ryujinx.Common.Logging;
  4  using Ryujinx.Memory;
  5  using System;
  6  using System.Diagnostics;
  7  using System.Linq;
  8  using System.Threading;
  9  
 10  namespace Ryujinx.Audio.Input
 11  {
 12      /// <summary>
 13      /// The audio input manager.
 14      /// </summary>
 15      public class AudioInputManager : IDisposable
 16      {
 17          private readonly object _lock = new();
 18  
 19          /// <summary>
 20          /// Lock used for session allocation.
 21          /// </summary>
 22          private readonly object _sessionLock = new();
 23  
 24          /// <summary>
 25          /// The session ids allocation table.
 26          /// </summary>
 27          private readonly int[] _sessionIds;
 28  
 29          /// <summary>
 30          /// The device driver.
 31          /// </summary>
 32          private IHardwareDeviceDriver _deviceDriver;
 33  
 34          /// <summary>
 35          /// The events linked to each session.
 36          /// </summary>
 37          private IWritableEvent[] _sessionsBufferEvents;
 38  
 39          /// <summary>
 40          /// The <see cref="AudioInputSystem"/> session instances.
 41          /// </summary>
 42          private readonly AudioInputSystem[] _sessions;
 43  
 44          /// <summary>
 45          /// The count of active sessions.
 46          /// </summary>
 47          private int _activeSessionCount;
 48  
 49          /// <summary>
 50          /// The dispose state.
 51          /// </summary>
 52          private int _disposeState;
 53  
 54          /// <summary>
 55          /// Create a new <see cref="AudioInputManager"/>.
 56          /// </summary>
 57          public AudioInputManager()
 58          {
 59              _sessionIds = new int[Constants.AudioInSessionCountMax];
 60              _sessions = new AudioInputSystem[Constants.AudioInSessionCountMax];
 61              _activeSessionCount = 0;
 62  
 63              for (int i = 0; i < _sessionIds.Length; i++)
 64              {
 65                  _sessionIds[i] = i;
 66              }
 67          }
 68  
 69          /// <summary>
 70          /// Initialize the <see cref="AudioInputManager"/>.
 71          /// </summary>
 72          /// <param name="deviceDriver">The device driver.</param>
 73          /// <param name="sessionRegisterEvents">The events associated to each session.</param>
 74          public void Initialize(IHardwareDeviceDriver deviceDriver, IWritableEvent[] sessionRegisterEvents)
 75          {
 76              _deviceDriver = deviceDriver;
 77              _sessionsBufferEvents = sessionRegisterEvents;
 78          }
 79  
 80          /// <summary>
 81          /// Acquire a new session id.
 82          /// </summary>
 83          /// <returns>A new session id.</returns>
 84          private int AcquireSessionId()
 85          {
 86              lock (_sessionLock)
 87              {
 88                  int index = _activeSessionCount;
 89  
 90                  Debug.Assert(index < _sessionIds.Length);
 91  
 92                  int sessionId = _sessionIds[index];
 93  
 94                  _sessionIds[index] = -1;
 95  
 96                  _activeSessionCount++;
 97  
 98                  Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new input ({sessionId})");
 99  
100                  return sessionId;
101              }
102          }
103  
104          /// <summary>
105          /// Release a given <paramref name="sessionId"/>.
106          /// </summary>
107          /// <param name="sessionId">The session id to release.</param>
108          private void ReleaseSessionId(int sessionId)
109          {
110              lock (_sessionLock)
111              {
112                  Debug.Assert(_activeSessionCount > 0);
113  
114                  int newIndex = --_activeSessionCount;
115  
116                  _sessionIds[newIndex] = sessionId;
117              }
118  
119              Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered input ({sessionId})");
120          }
121  
122          /// <summary>
123          /// Used to update audio input system.
124          /// </summary>
125          public void Update()
126          {
127              lock (_sessionLock)
128              {
129                  foreach (AudioInputSystem input in _sessions)
130                  {
131                      input?.Update();
132                  }
133              }
134          }
135  
136          /// <summary>
137          /// Register a new <see cref="AudioInputSystem"/>.
138          /// </summary>
139          /// <param name="input">The <see cref="AudioInputSystem"/> to register.</param>
140          private void Register(AudioInputSystem input)
141          {
142              lock (_sessionLock)
143              {
144                  _sessions[input.GetSessionId()] = input;
145              }
146          }
147  
148          /// <summary>
149          /// Unregister a new <see cref="AudioInputSystem"/>.
150          /// </summary>
151          /// <param name="input">The <see cref="AudioInputSystem"/> to unregister.</param>
152          internal void Unregister(AudioInputSystem input)
153          {
154              lock (_sessionLock)
155              {
156                  int sessionId = input.GetSessionId();
157  
158                  _sessions[input.GetSessionId()] = null;
159  
160                  ReleaseSessionId(sessionId);
161              }
162          }
163  
164          /// <summary>
165          /// Get the list of all audio inputs names.
166          /// </summary>
167          /// <param name="filtered">If true, filter disconnected devices</param>
168          /// <returns>The list of all audio inputs name</returns>
169          public string[] ListAudioIns(bool filtered)
170          {
171              if (filtered)
172              {
173                  // TODO: Detect if the driver supports audio input
174              }
175  
176              return new[] { Constants.DefaultDeviceInputName };
177          }
178  
179          /// <summary>
180          /// Open a new <see cref="AudioInputSystem"/>.
181          /// </summary>
182          /// <param name="outputDeviceName">The output device name selected by the <see cref="AudioInputSystem"/></param>
183          /// <param name="outputConfiguration">The output audio configuration selected by the <see cref="AudioInputSystem"/></param>
184          /// <param name="obj">The new <see cref="AudioInputSystem"/></param>
185          /// <param name="memoryManager">The memory manager that will be used for all guest memory operations</param>
186          /// <param name="inputDeviceName">The input device name wanted by the user</param>
187          /// <param name="sampleFormat">The sample format to use</param>
188          /// <param name="parameter">The user configuration</param>
189          /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
190          public ResultCode OpenAudioIn(out string outputDeviceName,
191                                        out AudioOutputConfiguration outputConfiguration,
192                                        out AudioInputSystem obj,
193                                        IVirtualMemoryManager memoryManager,
194                                        string inputDeviceName,
195                                        SampleFormat sampleFormat,
196                                        ref AudioInputConfiguration parameter)
197          {
198              int sessionId = AcquireSessionId();
199  
200              _sessionsBufferEvents[sessionId].Clear();
201  
202              IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Input, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount);
203  
204              AudioInputSystem audioIn = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
205  
206              ResultCode result = audioIn.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId);
207  
208              if (result == ResultCode.Success)
209              {
210                  outputDeviceName = audioIn.DeviceName;
211                  outputConfiguration = new AudioOutputConfiguration
212                  {
213                      ChannelCount = audioIn.ChannelCount,
214                      SampleFormat = audioIn.SampleFormat,
215                      SampleRate = audioIn.SampleRate,
216                      AudioOutState = audioIn.GetState(),
217                  };
218  
219                  obj = audioIn;
220  
221                  Register(audioIn);
222              }
223              else
224              {
225                  ReleaseSessionId(sessionId);
226  
227                  obj = null;
228                  outputDeviceName = null;
229                  outputConfiguration = default;
230              }
231  
232              return result;
233          }
234  
235          public void Dispose()
236          {
237              GC.SuppressFinalize(this);
238  
239              if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
240              {
241                  Dispose(true);
242              }
243          }
244  
245          protected virtual void Dispose(bool disposing)
246          {
247              if (disposing)
248              {
249                  // Clone the sessions array to dispose them outside the lock.
250                  AudioInputSystem[] sessions;
251  
252                  lock (_sessionLock)
253                  {
254                      sessions = _sessions.ToArray();
255                  }
256  
257                  foreach (AudioInputSystem input in sessions)
258                  {
259                      input?.Dispose();
260                  }
261              }
262          }
263      }
264  }