using Ryujinx.Audio.Common; using Ryujinx.Audio.Integration; using Ryujinx.Common.Logging; using Ryujinx.Memory; using System; using System.Diagnostics; using System.Linq; using System.Threading; namespace Ryujinx.Audio.Output { /// /// The audio output manager. /// public class AudioOutputManager : IDisposable { private readonly object _lock = new(); /// /// Lock used for session allocation. /// private readonly object _sessionLock = new(); /// /// The session ids allocation table. /// private int[] _sessionIds; /// /// The device driver. /// private IHardwareDeviceDriver _deviceDriver; /// /// The events linked to each session. /// private IWritableEvent[] _sessionsBufferEvents; /// /// The session instances. /// private AudioOutputSystem[] _sessions; /// /// The count of active sessions. /// private int _activeSessionCount; /// /// The dispose state. /// private int _disposeState; /// /// Create a new . /// public AudioOutputManager() { _sessionIds = new int[Constants.AudioOutSessionCountMax]; _sessions = new AudioOutputSystem[Constants.AudioOutSessionCountMax]; _activeSessionCount = 0; for (int i = 0; i < _sessionIds.Length; i++) { _sessionIds[i] = i; } } /// /// Initialize the . /// /// The device driver. /// The events associated to each session. public void Initialize(IHardwareDeviceDriver deviceDriver, IWritableEvent[] sessionRegisterEvents) { _deviceDriver = deviceDriver; _sessionsBufferEvents = sessionRegisterEvents; } /// /// Acquire a new session id. /// /// A new session id. 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 output ({sessionId})"); return sessionId; } } /// /// Release a given . /// /// The session id to release. private void ReleaseSessionId(int sessionId) { lock (_sessionLock) { Debug.Assert(_activeSessionCount > 0); int newIndex = --_activeSessionCount; _sessionIds[newIndex] = sessionId; } Logger.Info?.Print(LogClass.AudioRenderer, $"Unregistered output ({sessionId})"); } /// /// Used to update audio output system. /// public void Update() { lock (_sessionLock) { foreach (AudioOutputSystem output in _sessions) { output?.Update(); } } } /// /// Register a new . /// /// The to register. private void Register(AudioOutputSystem output) { lock (_sessionLock) { _sessions[output.GetSessionId()] = output; } } /// /// Unregister a new . /// /// The to unregister. internal void Unregister(AudioOutputSystem output) { lock (_sessionLock) { int sessionId = output.GetSessionId(); _sessions[output.GetSessionId()] = null; ReleaseSessionId(sessionId); } } /// /// Get the list of all audio outputs name. /// /// The list of all audio outputs name public string[] ListAudioOuts() { return new string[] { Constants.DefaultDeviceOutputName }; } /// /// Open a new . /// /// The output device name selected by the /// The output audio configuration selected by the /// The new /// The memory manager that will be used for all guest memory operations /// The input device name wanted by the user /// The sample format to use /// The user configuration /// The applet resource user id of the application /// The process handle of the application /// A reporting an error or a success public ResultCode OpenAudioOut(out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out AudioOutputSystem obj, IVirtualMemoryManager memoryManager, string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume) { int sessionId = AcquireSessionId(); _sessionsBufferEvents[sessionId].Clear(); IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume); AudioOutputSystem audioOut = new AudioOutputSystem(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); ResultCode result = audioOut.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId); if (result == ResultCode.Success) { outputDeviceName = audioOut.DeviceName; outputConfiguration = new AudioOutputConfiguration { ChannelCount = audioOut.ChannelCount, SampleFormat = audioOut.SampleFormat, SampleRate = audioOut.SampleRate, AudioOutState = audioOut.GetState(), }; obj = audioOut; Register(audioOut); } else { ReleaseSessionId(sessionId); obj = null; outputDeviceName = null; outputConfiguration = default; } return result; } /// /// Sets the volume for all output devices. /// /// The volume to set. public void SetVolume(float volume) { if (_sessions != null) { foreach (AudioOutputSystem session in _sessions) { session?.SetVolume(volume); } } } /// /// Gets the volume for all output devices. /// /// A float indicating the volume level. public float GetVolume() { if (_sessions != null) { foreach (AudioOutputSystem session in _sessions) { if (session != null) { return session.GetVolume(); } } } return 0.0f; } 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. AudioOutputSystem[] sessions; lock (_sessionLock) { sessions = _sessions.ToArray(); } foreach (AudioOutputSystem output in sessions) { output?.Dispose(); } } } } }