aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs')
-rw-r--r--Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs333
1 files changed, 333 insertions, 0 deletions
diff --git a/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs b/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs
new file mode 100644
index 00000000..023dd477
--- /dev/null
+++ b/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs
@@ -0,0 +1,333 @@
+//
+// Copyright (c) 2019-2020 Ryujinx
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with this program. If not, see <https://www.gnu.org/licenses/>.
+//
+
+using Ryujinx.Audio.Renderer.Dsp;
+using Ryujinx.Audio.Renderer.Integration;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using System;
+using System.Diagnostics;
+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 output devices associated to each session.
+ /// </summary>
+ // TODO: get ride of this with the audout rewrite.
+ public HardwareDevice[] OutputDevices { get; private set; }
+
+ /// <summary>
+ /// The <see cref="AudioProcessor"/> instance associated to this manager.
+ /// </summary>
+ public AudioProcessor Processor { get; }
+
+ /// <summary>
+ /// Create a new <see cref="AudioRendererManager"/>.
+ /// </summary>
+ public AudioRendererManager()
+ {
+ Processor = new AudioProcessor();
+ _sessionIds = new int[RendererConstants.AudioRendererSessionCountMax];
+ _sessions = new AudioRenderSystem[RendererConstants.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="devices">The output devices associated to each session.</param>
+ public void Initialize(IWritableEvent[] sessionSystemEvents, HardwareDevice[] devices)
+ {
+ _sessionsSystemEvent = sessionSystemEvents;
+ OutputDevices = devices;
+ }
+
+ /// <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()
+ {
+ _isRunning = true;
+
+ // TODO: virtual device mapping (IAudioDevice)
+ Processor.SetOutputDevices(OutputDevices);
+ Processor.Start();
+
+ _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>
+ /// 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)
+ {
+ lock (_sessionLock)
+ {
+ _sessions[renderer.GetSessionId()] = renderer;
+ }
+
+ lock (_audioProcessorLock)
+ {
+ if (!_isRunning)
+ {
+ StartLocked();
+ }
+ }
+ }
+
+ /// <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, MemoryManager memoryManager, ref AudioRendererConfiguration parameter, ulong appletResourceUserId, ulong workBufferAddress, ulong workBufferSize, uint processHandle)
+ {
+ int sessionId = AcquireSessionId();
+
+ AudioRenderSystem audioRenderer = new AudioRenderSystem(this, _sessionsSystemEvent[sessionId]);
+
+ ResultCode result = audioRenderer.Initialize(ref parameter, processHandle, workBufferAddress, workBufferSize, sessionId, appletResourceUserId, memoryManager);
+
+ if (result == ResultCode.Success)
+ {
+ renderer = audioRenderer;
+
+ Register(renderer);
+ }
+ else
+ {
+ ReleaseSessionId(sessionId);
+
+ renderer = null;
+ }
+
+ return result;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ Processor.Dispose();
+
+ foreach (HardwareDevice device in OutputDevices)
+ {
+ device.Dispose();
+ }
+ }
+ }
+ }
+}