aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs')
-rw-r--r--src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs405
1 files changed, 405 insertions, 0 deletions
diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
new file mode 100644
index 00000000..4de0ad16
--- /dev/null
+++ b/src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs
@@ -0,0 +1,405 @@
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Dsp;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.Memory;
+using System;
+using System.Diagnostics;
+using System.Linq;
+using System.Threading;
+
+namespace Ryujinx.Audio.Renderer.Server
+{
+ /// <summary>
+ /// The audio renderer manager.
+ /// </summary>
+ public class AudioRendererManager : IDisposable
+ {
+ /// <summary>
+ /// Lock used for session allocation.
+ /// </summary>
+ private object _sessionLock = new object();
+
+ /// <summary>
+ /// Lock used to control the <see cref="AudioProcessor"/> running state.
+ /// </summary>
+ private object _audioProcessorLock = new object();
+
+ /// <summary>
+ /// The session ids allocation table.
+ /// </summary>
+ private int[] _sessionIds;
+
+ /// <summary>
+ /// The events linked to each session.
+ /// </summary>
+ private IWritableEvent[] _sessionsSystemEvent;
+
+ /// <summary>
+ /// The <see cref="AudioRenderSystem"/> sessions instances.
+ /// </summary>
+ private AudioRenderSystem[] _sessions;
+
+ /// <summary>
+ /// The count of active sessions.
+ /// </summary>
+ private int _activeSessionCount;
+
+ /// <summary>
+ /// The worker thread used to run <see cref="SendCommands"/>.
+ /// </summary>
+ private Thread _workerThread;
+
+ /// <summary>
+ /// Indicate if the worker thread and <see cref="AudioProcessor"/> are running.
+ /// </summary>
+ private bool _isRunning;
+
+ /// <summary>
+ /// The audio device driver to create audio outputs.
+ /// </summary>
+ private IHardwareDeviceDriver _deviceDriver;
+
+ /// <summary>
+ /// Tick source used to measure elapsed time.
+ /// </summary>
+ public ITickSource TickSource { get; }
+
+ /// <summary>
+ /// The <see cref="AudioProcessor"/> instance associated to this manager.
+ /// </summary>
+ public AudioProcessor Processor { get; }
+
+ /// <summary>
+ /// The dispose state.
+ /// </summary>
+ private int _disposeState;
+
+ /// <summary>
+ /// Create a new <see cref="AudioRendererManager"/>.
+ /// </summary>
+ /// <param name="tickSource">Tick source used to measure elapsed time.</param>
+ public AudioRendererManager(ITickSource tickSource)
+ {
+ Processor = new AudioProcessor();
+ TickSource = tickSource;
+ _sessionIds = new int[Constants.AudioRendererSessionCountMax];
+ _sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax];
+ _activeSessionCount = 0;
+
+ for (int i = 0; i < _sessionIds.Length; i++)
+ {
+ _sessionIds[i] = i;
+ }
+ }
+
+ /// <summary>
+ /// Initialize the <see cref="AudioRendererManager"/>.
+ /// </summary>
+ /// <param name="sessionSystemEvents">The events associated to each session.</param>
+ /// <param name="deviceDriver">The device driver to use to create audio outputs.</param>
+ public void Initialize(IWritableEvent[] sessionSystemEvents, IHardwareDeviceDriver deviceDriver)
+ {
+ _sessionsSystemEvent = sessionSystemEvents;
+ _deviceDriver = deviceDriver;
+ }
+
+ /// <summary>
+ /// Get the work buffer size required by a session.
+ /// </summary>
+ /// <param name="parameter">The user configuration</param>
+ /// <returns>The work buffer size required by a session.</returns>
+ public static ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
+ {
+ return AudioRenderSystem.GetWorkBufferSize(ref parameter);
+ }
+
+ /// <summary>
+ /// Acquire a new session id.
+ /// </summary>
+ /// <returns>A new session id.</returns>
+ private int AcquireSessionId()
+ {
+ lock (_sessionLock)
+ {
+ int index = _activeSessionCount;
+
+ Debug.Assert(index < _sessionIds.Length);
+
+ int sessionId = _sessionIds[index];
+
+ _sessionIds[index] = -1;
+
+ _activeSessionCount++;
+
+ Logger.Info?.Print(LogClass.AudioRenderer, $"Registered new renderer ({sessionId})");
+
+ return sessionId;
+ }
+ }
+
+ /// <summary>
+ /// Release a given <paramref name="sessionId"/>.
+ /// </summary>
+ /// <param name="sessionId">The session id to release.</param>
+ private void ReleaseSessionId(int sessionId)
+ {
+ lock (_sessionLock)
+ {
+ Debug.Assert(_activeSessionCount > 0);
+
+ int newIndex = --_activeSessionCount;
+
+ _sessionIds[newIndex] = sessionId;
+ }
+
+ Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered renderer ({sessionId})");
+ }
+
+ /// <summary>
+ /// Check if there is any audio renderer active.
+ /// </summary>
+ /// <returns>Returns true if there is any audio renderer active.</returns>
+ private bool HasAnyActiveRendererLocked()
+ {
+ foreach (AudioRenderSystem renderer in _sessions)
+ {
+ if (renderer != null)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Start the <see cref="AudioProcessor"/> and worker thread.
+ /// </summary>
+ private void StartLocked(float volume)
+ {
+ _isRunning = true;
+
+ // TODO: virtual device mapping (IAudioDevice)
+ Processor.Start(_deviceDriver, volume);
+
+ _workerThread = new Thread(SendCommands)
+ {
+ Name = "AudioRendererManager.Worker"
+ };
+
+ _workerThread.Start();
+ }
+
+ /// <summary>
+ /// Stop the <see cref="AudioProcessor"/> and worker thread.
+ /// </summary>
+ private void StopLocked()
+ {
+ _isRunning = false;
+
+ _workerThread.Join();
+ Processor.Stop();
+
+ Logger.Info?.Print(LogClass.AudioRenderer, "Stopped audio renderer");
+ }
+
+ /// <summary>
+ /// Stop sending commands to the <see cref="AudioProcessor"/> without stopping the worker thread.
+ /// </summary>
+ public void StopSendingCommands()
+ {
+ lock (_sessionLock)
+ {
+ foreach (AudioRenderSystem renderer in _sessions)
+ {
+ renderer?.Disable();
+ }
+ }
+
+ lock (_audioProcessorLock)
+ {
+ if (_isRunning)
+ {
+ StopLocked();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Worker main function. This is used to dispatch audio renderer commands to the <see cref="AudioProcessor"/>.
+ /// </summary>
+ private void SendCommands()
+ {
+ Logger.Info?.Print(LogClass.AudioRenderer, "Starting audio renderer");
+ Processor.Wait();
+
+ while (_isRunning)
+ {
+ lock (_sessionLock)
+ {
+ foreach (AudioRenderSystem renderer in _sessions)
+ {
+ renderer?.SendCommands();
+ }
+ }
+
+ Processor.Signal();
+ Processor.Wait();
+ }
+ }
+
+ /// <summary>
+ /// Register a new <see cref="AudioRenderSystem"/>.
+ /// </summary>
+ /// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
+ private void Register(AudioRenderSystem renderer, float volume)
+ {
+ lock (_sessionLock)
+ {
+ _sessions[renderer.GetSessionId()] = renderer;
+ }
+
+ lock (_audioProcessorLock)
+ {
+ if (!_isRunning)
+ {
+ StartLocked(volume);
+ }
+ }
+ }
+
+ /// <summary>
+ /// Unregister a new <see cref="AudioRenderSystem"/>.
+ /// </summary>
+ /// <param name="renderer">The <see cref="AudioRenderSystem"/> to unregister.</param>
+ internal void Unregister(AudioRenderSystem renderer)
+ {
+ lock (_sessionLock)
+ {
+ int sessionId = renderer.GetSessionId();
+
+ _sessions[renderer.GetSessionId()] = null;
+
+ ReleaseSessionId(sessionId);
+ }
+
+ lock (_audioProcessorLock)
+ {
+ if (_isRunning && !HasAnyActiveRendererLocked())
+ {
+ StopLocked();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Open a new <see cref="AudioRenderSystem"/>
+ /// </summary>
+ /// <param name="renderer">The new <see cref="AudioRenderSystem"/></param>
+ /// <param name="memoryManager">The memory manager that will be used for all guest memory operations.</param>
+ /// <param name="parameter">The user configuration</param>
+ /// <param name="appletResourceUserId">The applet resource user id of the application.</param>
+ /// <param name="workBufferAddress">The guest work buffer address.</param>
+ /// <param name="workBufferSize">The guest work buffer size.</param>
+ /// <param name="processHandle">The process handle of the application.</param>
+ /// <returns>A <see cref="ResultCode"/> reporting an error or a success.</returns>
+ public ResultCode OpenAudioRenderer(
+ out AudioRenderSystem renderer,
+ IVirtualMemoryManager memoryManager,
+ ref AudioRendererConfiguration parameter,
+ ulong appletResourceUserId,
+ ulong workBufferAddress,
+ ulong workBufferSize,
+ uint processHandle,
+ float volume)
+ {
+ int sessionId = AcquireSessionId();
+
+ AudioRenderSystem audioRenderer = new AudioRenderSystem(this, _sessionsSystemEvent[sessionId]);
+
+ // TODO: Eventually, we should try to use the guest supplied work buffer instead of allocating
+ // our own. However, it was causing problems on some applications that would unmap the memory
+ // before the audio renderer was fully disposed.
+ Memory<byte> workBufferMemory = GC.AllocateArray<byte>((int)workBufferSize, pinned: true);
+
+ ResultCode result = audioRenderer.Initialize(
+ ref parameter,
+ processHandle,
+ workBufferMemory,
+ workBufferAddress,
+ workBufferSize,
+ sessionId,
+ appletResourceUserId,
+ memoryManager);
+
+ if (result == ResultCode.Success)
+ {
+ renderer = audioRenderer;
+
+ Register(renderer, volume);
+ }
+ else
+ {
+ ReleaseSessionId(sessionId);
+
+ renderer = null;
+ }
+
+ return result;
+ }
+
+ public float GetVolume()
+ {
+ if (Processor != null)
+ {
+ return Processor.GetVolume();
+ }
+
+ return 0f;
+ }
+
+ public void SetVolume(float volume)
+ {
+ Processor?.SetVolume(volume);
+ }
+
+ public void Dispose()
+ {
+ if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
+ {
+ Dispose(true);
+ }
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ // Clone the sessions array to dispose them outside the lock.
+ AudioRenderSystem[] sessions;
+
+ lock (_sessionLock)
+ {
+ sessions = _sessions.ToArray();
+ }
+
+ foreach (AudioRenderSystem renderer in sessions)
+ {
+ renderer?.Dispose();
+ }
+
+ lock (_audioProcessorLock)
+ {
+ if (_isRunning)
+ {
+ StopLocked();
+ }
+ }
+
+ Processor.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file