/ src / Ryujinx.Audio / Output / AudioOutputSystem.cs
AudioOutputSystem.cs
  1  using Ryujinx.Audio.Common;
  2  using Ryujinx.Audio.Integration;
  3  using System;
  4  using System.Threading;
  5  
  6  namespace Ryujinx.Audio.Output
  7  {
  8      /// <summary>
  9      /// Audio output system.
 10      /// </summary>
 11      public class AudioOutputSystem : IDisposable
 12      {
 13          /// <summary>
 14          /// The session id associated to the <see cref="AudioOutputSystem"/>.
 15          /// </summary>
 16          private int _sessionId;
 17  
 18          /// <summary>
 19          /// The session the <see cref="AudioOutputSystem"/>.
 20          /// </summary>
 21          private readonly AudioDeviceSession _session;
 22  
 23          /// <summary>
 24          /// The target device name of the <see cref="AudioOutputSystem"/>.
 25          /// </summary>
 26          public string DeviceName { get; private set; }
 27  
 28          /// <summary>
 29          /// The target sample rate of the <see cref="AudioOutputSystem"/>.
 30          /// </summary>
 31          public uint SampleRate { get; private set; }
 32  
 33          /// <summary>
 34          /// The target channel count of the <see cref="AudioOutputSystem"/>.
 35          /// </summary>
 36          public uint ChannelCount { get; private set; }
 37  
 38          /// <summary>
 39          /// The target sample format of the <see cref="AudioOutputSystem"/>.
 40          /// </summary>
 41          public SampleFormat SampleFormat { get; private set; }
 42  
 43          /// <summary>
 44          /// The <see cref="AudioOutputManager"/> owning this.
 45          /// </summary>
 46          private readonly AudioOutputManager _manager;
 47  
 48          /// <summary>
 49          /// THe lock of the parent.
 50          /// </summary>
 51          private readonly object _parentLock;
 52  
 53          /// <summary>
 54          /// The dispose state.
 55          /// </summary>
 56          private int _disposeState;
 57  
 58          /// <summary>
 59          /// Create a new <see cref="AudioOutputSystem"/>.
 60          /// </summary>
 61          /// <param name="manager">The manager instance</param>
 62          /// <param name="parentLock">The lock of the manager</param>
 63          /// <param name="deviceSession">The hardware device session</param>
 64          /// <param name="bufferEvent">The buffer release event of the audio output</param>
 65          public AudioOutputSystem(AudioOutputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent)
 66          {
 67              _manager = manager;
 68              _parentLock = parentLock;
 69              _session = new AudioDeviceSession(deviceSession, bufferEvent);
 70          }
 71  
 72          /// <summary>
 73          /// Get the default device name on the system.
 74          /// </summary>
 75          /// <returns>The default device name on the system.</returns>
 76          private static string GetDeviceDefaultName()
 77          {
 78              return Constants.DefaultDeviceOutputName;
 79          }
 80  
 81          /// <summary>
 82          /// Check if a given configuration and device name is valid on the system.
 83          /// </summary>
 84          /// <param name="configuration">The configuration to check.</param>
 85          /// <param name="deviceName">The device name to check.</param>
 86          /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
 87          private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName)
 88          {
 89              if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName()))
 90              {
 91                  return ResultCode.DeviceNotFound;
 92              }
 93  
 94              if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate)
 95              {
 96                  return ResultCode.UnsupportedSampleRate;
 97              }
 98  
 99              if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6)
100              {
101                  return ResultCode.UnsupportedChannelConfiguration;
102              }
103  
104              return ResultCode.Success;
105          }
106  
107          /// <summary>
108          /// Get the released buffer event.
109          /// </summary>
110          /// <returns>The released buffer event</returns>
111          public IWritableEvent RegisterBufferEvent()
112          {
113              lock (_parentLock)
114              {
115                  return _session.GetBufferEvent();
116              }
117          }
118  
119          /// <summary>
120          /// Update the <see cref="AudioOutputSystem"/>.
121          /// </summary>
122          public void Update()
123          {
124              lock (_parentLock)
125              {
126                  _session.Update();
127              }
128          }
129  
130          /// <summary>
131          /// Get the id of this session.
132          /// </summary>
133          /// <returns>The id of this session</returns>
134          public int GetSessionId()
135          {
136              return _sessionId;
137          }
138  
139          /// <summary>
140          /// Initialize the <see cref="AudioOutputSystem"/>.
141          /// </summary>
142          /// <param name="inputDeviceName">The input device name wanted by the user</param>
143          /// <param name="sampleFormat">The sample format to use</param>
144          /// <param name="parameter">The user configuration</param>
145          /// <param name="sessionId">The session id associated to this <see cref="AudioOutputSystem"/></param>
146          /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
147          public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId)
148          {
149              _sessionId = sessionId;
150  
151              ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName);
152  
153              if (result == ResultCode.Success)
154              {
155                  if (inputDeviceName.Length == 0)
156                  {
157                      DeviceName = GetDeviceDefaultName();
158                  }
159                  else
160                  {
161                      DeviceName = inputDeviceName;
162                  }
163  
164                  if (parameter.ChannelCount == 6)
165                  {
166                      ChannelCount = 6;
167                  }
168                  else
169                  {
170                      ChannelCount = 2;
171                  }
172  
173                  SampleFormat = sampleFormat;
174                  SampleRate = Constants.TargetSampleRate;
175              }
176  
177              return result;
178          }
179  
180          /// <summary>
181          /// Append a new audio buffer to the audio output.
182          /// </summary>
183          /// <param name="bufferTag">The unique tag of this buffer.</param>
184          /// <param name="userBuffer">The buffer informations.</param>
185          /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
186          public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer)
187          {
188              lock (_parentLock)
189              {
190                  AudioBuffer buffer = new()
191                  {
192                      BufferTag = bufferTag,
193                      DataPointer = userBuffer.Data,
194                      DataSize = userBuffer.DataSize,
195                  };
196  
197                  if (_session.AppendBuffer(buffer))
198                  {
199                      return ResultCode.Success;
200                  }
201  
202                  return ResultCode.BufferRingFull;
203              }
204          }
205  
206          /// <summary>
207          /// Get the release buffers.
208          /// </summary>
209          /// <param name="releasedBuffers">The buffer to write the release buffers</param>
210          /// <param name="releasedCount">The count of released buffers</param>
211          /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
212          public ResultCode GetReleasedBuffer(Span<ulong> releasedBuffers, out uint releasedCount)
213          {
214              releasedCount = 0;
215  
216              // Ensure that the first entry is set to zero if no entries are returned.
217              if (releasedBuffers.Length > 0)
218              {
219                  releasedBuffers[0] = 0;
220              }
221  
222              lock (_parentLock)
223              {
224                  for (int i = 0; i < releasedBuffers.Length; i++)
225                  {
226                      if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer))
227                      {
228                          break;
229                      }
230  
231                      releasedBuffers[i] = buffer.BufferTag;
232                      releasedCount++;
233                  }
234              }
235  
236              return ResultCode.Success;
237          }
238  
239          /// <summary>
240          /// Get the current state of the <see cref="AudioOutputSystem"/>.
241          /// </summary>
242          /// <returns>Return the curent sta\te of the <see cref="AudioOutputSystem"/></returns>
243          /// <returns></returns>
244          public AudioDeviceState GetState()
245          {
246              lock (_parentLock)
247              {
248                  return _session.GetState();
249              }
250          }
251  
252          /// <summary>
253          /// Start the audio session.
254          /// </summary>
255          /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
256          public ResultCode Start()
257          {
258              lock (_parentLock)
259              {
260                  return _session.Start();
261              }
262          }
263  
264          /// <summary>
265          /// Stop the audio session.
266          /// </summary>
267          /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
268          public ResultCode Stop()
269          {
270              lock (_parentLock)
271              {
272                  return _session.Stop();
273              }
274          }
275  
276          /// <summary>
277          /// Get the volume of the session.
278          /// </summary>
279          /// <returns>The volume of the session</returns>
280          public float GetVolume()
281          {
282              lock (_parentLock)
283              {
284                  return _session.GetVolume();
285              }
286          }
287  
288          /// <summary>
289          /// Set the volume of the session.
290          /// </summary>
291          /// <param name="volume">The new volume to set</param>
292          public void SetVolume(float volume)
293          {
294              lock (_parentLock)
295              {
296                  _session.SetVolume(volume);
297              }
298          }
299  
300          /// <summary>
301          /// Get the count of buffer currently in use (server + driver side).
302          /// </summary>
303          /// <returns>The count of buffer currently in use</returns>
304          public uint GetBufferCount()
305          {
306              lock (_parentLock)
307              {
308                  return _session.GetBufferCount();
309              }
310          }
311  
312          /// <summary>
313          /// Check if a buffer is present.
314          /// </summary>
315          /// <param name="bufferTag">The unique tag of the buffer</param>
316          /// <returns>Return true if a buffer is present</returns>
317          public bool ContainsBuffer(ulong bufferTag)
318          {
319              lock (_parentLock)
320              {
321                  return _session.ContainsBuffer(bufferTag);
322              }
323          }
324  
325          /// <summary>
326          /// Get the count of sample played in this session.
327          /// </summary>
328          /// <returns>The count of sample played in this session</returns>
329          public ulong GetPlayedSampleCount()
330          {
331              lock (_parentLock)
332              {
333                  return _session.GetPlayedSampleCount();
334              }
335          }
336  
337          /// <summary>
338          /// Flush all buffers to the initial state.
339          /// </summary>
340          /// <returns>True if any buffers was flushed</returns>
341          public bool FlushBuffers()
342          {
343              lock (_parentLock)
344              {
345                  return _session.FlushBuffers();
346              }
347          }
348  
349          public void Dispose()
350          {
351              GC.SuppressFinalize(this);
352  
353              if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
354              {
355                  Dispose(true);
356              }
357          }
358  
359          protected virtual void Dispose(bool disposing)
360          {
361              if (disposing)
362              {
363                  _session.Dispose();
364  
365                  _manager.Unregister(this);
366              }
367          }
368      }
369  }