diff options
Diffstat (limited to 'src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs')
-rw-r--r-- | src/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs | 405 |
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 |