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();
}
}
}
}
}