/ src / Ryujinx.Audio / Common / AudioDeviceSession.cs
AudioDeviceSession.cs
  1  using Ryujinx.Audio.Integration;
  2  using Ryujinx.Common;
  3  using System;
  4  using System.Diagnostics;
  5  
  6  namespace Ryujinx.Audio.Common
  7  {
  8      /// <summary>
  9      /// An audio device session.
 10      /// </summary>
 11      class AudioDeviceSession : IDisposable
 12      {
 13          /// <summary>
 14          /// The volume of the <see cref="AudioDeviceSession"/>.
 15          /// </summary>
 16          private float _volume;
 17  
 18          /// <summary>
 19          /// The state of the <see cref="AudioDeviceSession"/>.
 20          /// </summary>
 21          private AudioDeviceState _state;
 22  
 23          /// <summary>
 24          /// Array of all buffers currently used or released.
 25          /// </summary>
 26          private readonly AudioBuffer[] _buffers;
 27  
 28          /// <summary>
 29          /// The server index inside <see cref="_buffers"/> (appended but not queued to device driver).
 30          /// </summary>
 31          private uint _serverBufferIndex;
 32  
 33          /// <summary>
 34          /// The hardware index inside <see cref="_buffers"/> (queued to device driver).
 35          /// </summary>
 36          private uint _hardwareBufferIndex;
 37  
 38          /// <summary>
 39          /// The released index inside <see cref="_buffers"/> (released by the device driver).
 40          /// </summary>
 41          private uint _releasedBufferIndex;
 42  
 43          /// <summary>
 44          /// The count of buffer appended (server side).
 45          /// </summary>
 46          private uint _bufferAppendedCount;
 47  
 48          /// <summary>
 49          /// The count of buffer registered (driver side).
 50          /// </summary>
 51          private uint _bufferRegisteredCount;
 52  
 53          /// <summary>
 54          /// The count of buffer released (released by the driver side).
 55          /// </summary>
 56          private uint _bufferReleasedCount;
 57  
 58          /// <summary>
 59          /// The released buffer event.
 60          /// </summary>
 61          private readonly IWritableEvent _bufferEvent;
 62  
 63          /// <summary>
 64          /// The session on the device driver.
 65          /// </summary>
 66          private readonly IHardwareDeviceSession _hardwareDeviceSession;
 67  
 68          /// <summary>
 69          /// Max number of buffers that can be registered to the device driver at a time.
 70          /// </summary>
 71          private readonly uint _bufferRegisteredLimit;
 72  
 73          /// <summary>
 74          /// Create a new <see cref="AudioDeviceSession"/>.
 75          /// </summary>
 76          /// <param name="deviceSession">The device driver session associated</param>
 77          /// <param name="bufferEvent">The release buffer event</param>
 78          /// <param name="bufferRegisteredLimit">The max number of buffers that can be registered to the device driver at a time</param>
 79          public AudioDeviceSession(IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent, uint bufferRegisteredLimit = 4)
 80          {
 81              _bufferEvent = bufferEvent;
 82              _hardwareDeviceSession = deviceSession;
 83              _bufferRegisteredLimit = bufferRegisteredLimit;
 84  
 85              _buffers = new AudioBuffer[Constants.AudioDeviceBufferCountMax];
 86              _serverBufferIndex = 0;
 87              _hardwareBufferIndex = 0;
 88              _releasedBufferIndex = 0;
 89  
 90              _bufferAppendedCount = 0;
 91              _bufferRegisteredCount = 0;
 92              _bufferReleasedCount = 0;
 93              _volume = deviceSession.GetVolume();
 94              _state = AudioDeviceState.Stopped;
 95          }
 96  
 97          /// <summary>
 98          /// Get the released buffer event.
 99          /// </summary>
100          /// <returns>The released buffer event</returns>
101          public IWritableEvent GetBufferEvent()
102          {
103              return _bufferEvent;
104          }
105  
106          /// <summary>
107          /// Get the state of the session.
108          /// </summary>
109          /// <returns>The state of the session</returns>
110          public AudioDeviceState GetState()
111          {
112              Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped);
113  
114              return _state;
115          }
116  
117          /// <summary>
118          /// Get the total buffer count (server + driver + released).
119          /// </summary>
120          /// <returns>Return the total buffer count</returns>
121          private uint GetTotalBufferCount()
122          {
123              uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount;
124  
125              Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax);
126  
127              return bufferCount;
128          }
129  
130          /// <summary>
131          /// Register a new <see cref="AudioBuffer"/> on the server side.
132          /// </summary>
133          /// <param name="buffer">The <see cref="AudioBuffer"/> to register</param>
134          /// <returns>True if the operation succeeded</returns>
135          private bool RegisterBuffer(AudioBuffer buffer)
136          {
137              if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax)
138              {
139                  return false;
140              }
141  
142              _buffers[_serverBufferIndex] = buffer;
143              _serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax;
144              _bufferAppendedCount++;
145  
146              return true;
147          }
148  
149          /// <summary>
150          /// Flush server buffers to hardware.
151          /// </summary>
152          private void FlushToHardware()
153          {
154              uint bufferToFlushCount = Math.Min(Math.Min(_bufferAppendedCount, 4), _bufferRegisteredLimit - _bufferRegisteredCount);
155  
156              AudioBuffer[] buffersToFlush = new AudioBuffer[bufferToFlushCount];
157  
158              uint hardwareBufferIndex = _hardwareBufferIndex;
159  
160              for (int i = 0; i < buffersToFlush.Length; i++)
161              {
162                  buffersToFlush[i] = _buffers[hardwareBufferIndex];
163  
164                  _bufferAppendedCount--;
165                  _bufferRegisteredCount++;
166  
167                  hardwareBufferIndex = (hardwareBufferIndex + 1) % Constants.AudioDeviceBufferCountMax;
168              }
169  
170              _hardwareBufferIndex = hardwareBufferIndex;
171  
172              for (int i = 0; i < buffersToFlush.Length; i++)
173              {
174                  _hardwareDeviceSession.QueueBuffer(buffersToFlush[i]);
175              }
176          }
177  
178          /// <summary>
179          /// Get the current index of the <see cref="AudioBuffer"/> playing on the driver side.
180          /// </summary>
181          /// <param name="playingIndex">The output index of the <see cref="AudioBuffer"/> playing on the driver side</param>
182          /// <returns>True if any buffer is playing</returns>
183          private bool TryGetPlayingBufferIndex(out uint playingIndex)
184          {
185              if (_bufferRegisteredCount > 0)
186              {
187                  playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax;
188  
189                  return true;
190              }
191  
192              playingIndex = 0;
193  
194              return false;
195          }
196  
197          /// <summary>
198          /// Try to pop the <see cref="AudioBuffer"/> playing on the driver side.
199          /// </summary>
200          /// <param name="buffer">The output <see cref="AudioBuffer"/> playing on the driver side</param>
201          /// <returns>True if any buffer is playing</returns>
202          private bool TryPopPlayingBuffer(out AudioBuffer buffer)
203          {
204              if (_bufferRegisteredCount > 0)
205              {
206                  uint bufferIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax;
207  
208                  buffer = _buffers[bufferIndex];
209  
210                  _buffers[bufferIndex] = null;
211  
212                  _bufferRegisteredCount--;
213  
214                  return true;
215              }
216  
217              buffer = null;
218  
219              return false;
220          }
221  
222          /// <summary>
223          /// Try to pop a <see cref="AudioBuffer"/> released by the driver side.
224          /// </summary>
225          /// <param name="buffer">The output <see cref="AudioBuffer"/> released by the driver side</param>
226          /// <returns>True if any buffer has been released</returns>
227          public bool TryPopReleasedBuffer(out AudioBuffer buffer)
228          {
229              if (_bufferReleasedCount > 0)
230              {
231                  uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
232  
233                  buffer = _buffers[bufferIndex];
234  
235                  _buffers[bufferIndex] = null;
236  
237                  _bufferReleasedCount--;
238  
239                  return true;
240              }
241  
242              buffer = null;
243  
244              return false;
245          }
246  
247          /// <summary>
248          /// Release a <see cref="AudioBuffer"/>.
249          /// </summary>
250          /// <param name="buffer">The <see cref="AudioBuffer"/> to release</param>
251          private void ReleaseBuffer(AudioBuffer buffer)
252          {
253              buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds;
254  
255              _bufferRegisteredCount--;
256              _bufferReleasedCount++;
257  
258              _releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax;
259          }
260  
261          /// <summary>
262          /// Update the released buffers.
263          /// </summary>
264          /// <param name="updateForStop">True if the session is currently stopping</param>
265          private void UpdateReleaseBuffers(bool updateForStop = false)
266          {
267              bool wasAnyBuffersReleased = false;
268  
269              while (TryGetPlayingBufferIndex(out uint playingIndex))
270              {
271                  if (!updateForStop && !_hardwareDeviceSession.WasBufferFullyConsumed(_buffers[playingIndex]))
272                  {
273                      break;
274                  }
275  
276                  if (updateForStop)
277                  {
278                      _hardwareDeviceSession.UnregisterBuffer(_buffers[playingIndex]);
279                  }
280  
281                  ReleaseBuffer(_buffers[playingIndex]);
282  
283                  wasAnyBuffersReleased = true;
284              }
285  
286              if (wasAnyBuffersReleased)
287              {
288                  _bufferEvent.Signal();
289              }
290          }
291  
292          /// <summary>
293          /// Append a new <see cref="AudioBuffer"/>.
294          /// </summary>
295          /// <param name="buffer">The <see cref="AudioBuffer"/> to append</param>
296          /// <returns>True if the buffer was appended</returns>
297          public bool AppendBuffer(AudioBuffer buffer)
298          {
299              if (_hardwareDeviceSession.RegisterBuffer(buffer))
300              {
301                  if (RegisterBuffer(buffer))
302                  {
303                      FlushToHardware();
304  
305                      return true;
306                  }
307  
308                  _hardwareDeviceSession.UnregisterBuffer(buffer);
309              }
310  
311              return false;
312          }
313  
314          public static bool AppendUacBuffer(AudioBuffer buffer, uint handle)
315          {
316              // NOTE: On hardware, there is another RegisterBuffer method taking a handle.
317              // This variant of the call always return false (stubbed?) as a result this logic will never succeed.
318  
319              return false;
320          }
321  
322          /// <summary>
323          /// Start the audio session.
324          /// </summary>
325          /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
326          public ResultCode Start()
327          {
328              if (_state == AudioDeviceState.Started)
329              {
330                  return ResultCode.OperationFailed;
331              }
332  
333              _hardwareDeviceSession.Start();
334  
335              _state = AudioDeviceState.Started;
336  
337              FlushToHardware();
338  
339              _hardwareDeviceSession.SetVolume(_volume);
340  
341              return ResultCode.Success;
342          }
343  
344          /// <summary>
345          /// Stop the audio session.
346          /// </summary>
347          /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
348          public ResultCode Stop()
349          {
350              if (_state == AudioDeviceState.Started)
351              {
352                  _hardwareDeviceSession.Stop();
353  
354                  UpdateReleaseBuffers(true);
355  
356                  _state = AudioDeviceState.Stopped;
357              }
358  
359              return ResultCode.Success;
360          }
361  
362          /// <summary>
363          /// Get the volume of the session.
364          /// </summary>
365          /// <returns>The volume of the session</returns>
366          public float GetVolume()
367          {
368              return _hardwareDeviceSession.GetVolume();
369          }
370  
371          /// <summary>
372          /// Set the volume of the session.
373          /// </summary>
374          /// <param name="volume">The new volume to set</param>
375          public void SetVolume(float volume)
376          {
377              _volume = volume;
378  
379              if (_state == AudioDeviceState.Started)
380              {
381                  _hardwareDeviceSession.SetVolume(volume);
382              }
383          }
384  
385          /// <summary>
386          /// Get the count of buffer currently in use (server + driver side).
387          /// </summary>
388          /// <returns>The count of buffer currently in use</returns>
389          public uint GetBufferCount()
390          {
391              return _bufferAppendedCount + _bufferRegisteredCount;
392          }
393  
394          /// <summary>
395          /// Check if a buffer is present.
396          /// </summary>
397          /// <param name="bufferTag">The unique tag of the buffer</param>
398          /// <returns>Return true if a buffer is present</returns>
399          public bool ContainsBuffer(ulong bufferTag)
400          {
401              uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
402  
403              uint totalBufferCount = GetTotalBufferCount();
404  
405              for (int i = 0; i < totalBufferCount; i++)
406              {
407                  if (_buffers[bufferIndex].BufferTag == bufferTag)
408                  {
409                      return true;
410                  }
411  
412                  bufferIndex = (bufferIndex + 1) % Constants.AudioDeviceBufferCountMax;
413              }
414  
415              return false;
416          }
417  
418          /// <summary>
419          /// Get the count of sample played in this session.
420          /// </summary>
421          /// <returns>The count of sample played in this session</returns>
422          public ulong GetPlayedSampleCount()
423          {
424              if (_state == AudioDeviceState.Stopped)
425              {
426                  return 0;
427              }
428  
429              return _hardwareDeviceSession.GetPlayedSampleCount();
430          }
431  
432          /// <summary>
433          /// Flush all buffers to the initial state.
434          /// </summary>
435          /// <returns>True if any buffer was flushed</returns>
436          public bool FlushBuffers()
437          {
438              if (_state == AudioDeviceState.Stopped)
439              {
440                  return false;
441              }
442  
443              uint bufferCount = GetBufferCount();
444  
445              while (TryPopReleasedBuffer(out AudioBuffer buffer))
446              {
447                  _hardwareDeviceSession.UnregisterBuffer(buffer);
448              }
449  
450              while (TryPopPlayingBuffer(out AudioBuffer buffer))
451              {
452                  _hardwareDeviceSession.UnregisterBuffer(buffer);
453              }
454  
455              if (_bufferRegisteredCount == 0 || (_bufferReleasedCount + _bufferAppendedCount) > Constants.AudioDeviceBufferCountMax)
456              {
457                  return false;
458              }
459  
460              _bufferReleasedCount += _bufferAppendedCount;
461              _releasedBufferIndex = (_releasedBufferIndex + _bufferAppendedCount) % Constants.AudioDeviceBufferCountMax;
462              _bufferAppendedCount = 0;
463              _hardwareBufferIndex = _serverBufferIndex;
464  
465              if (bufferCount > 0)
466              {
467                  _bufferEvent.Signal();
468              }
469  
470              return true;
471          }
472  
473          /// <summary>
474          /// Update the session.
475          /// </summary>
476          public void Update()
477          {
478              if (_state == AudioDeviceState.Started)
479              {
480                  UpdateReleaseBuffers();
481                  FlushToHardware();
482              }
483          }
484  
485          public void Dispose()
486          {
487              Dispose(true);
488          }
489  
490          protected virtual void Dispose(bool disposing)
491          {
492              if (disposing)
493              {
494                  // Tell the hardware session that we are ending.
495                  _hardwareDeviceSession.PrepareToClose();
496  
497                  // Unregister all buffers
498  
499                  while (TryPopReleasedBuffer(out AudioBuffer buffer))
500                  {
501                      _hardwareDeviceSession.UnregisterBuffer(buffer);
502                  }
503  
504                  while (TryPopPlayingBuffer(out AudioBuffer buffer))
505                  {
506                      _hardwareDeviceSession.UnregisterBuffer(buffer);
507                  }
508  
509                  // Finally dispose hardware session.
510                  _hardwareDeviceSession.Dispose();
511  
512                  _bufferEvent.Signal();
513              }
514          }
515      }
516  }