2022-12-10 16:57:01 -07:00
|
|
|
|
using Ryujinx.Audio.Backends.SoundIo.Native;
|
|
|
|
|
using Ryujinx.Audio.Common;
|
2021-02-25 17:11:56 -07:00
|
|
|
|
using Ryujinx.Audio.Integration;
|
|
|
|
|
using Ryujinx.Memory;
|
|
|
|
|
using System;
|
2021-08-04 12:28:33 -06:00
|
|
|
|
using System.Collections.Concurrent;
|
2021-02-25 17:11:56 -07:00
|
|
|
|
using System.Threading;
|
2022-12-10 16:57:01 -07:00
|
|
|
|
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
2023-03-04 06:43:08 -07:00
|
|
|
|
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
namespace Ryujinx.Audio.Backends.SoundIo
|
|
|
|
|
{
|
|
|
|
|
public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
|
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
private readonly SoundIoContext _audioContext;
|
|
|
|
|
private readonly SoundIoDeviceContext _audioDevice;
|
2021-08-04 12:28:33 -06:00
|
|
|
|
private readonly ManualResetEvent _updateRequiredEvent;
|
2021-09-11 14:08:25 -06:00
|
|
|
|
private readonly ManualResetEvent _pauseEvent;
|
2021-08-04 12:28:33 -06:00
|
|
|
|
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
2021-06-29 11:37:13 -06:00
|
|
|
|
private int _disposeState;
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
public SoundIoHardwareDeviceDriver()
|
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
_audioContext = SoundIoContext.Create();
|
2021-02-25 17:11:56 -07:00
|
|
|
|
_updateRequiredEvent = new ManualResetEvent(false);
|
2021-09-11 14:08:25 -06:00
|
|
|
|
_pauseEvent = new ManualResetEvent(true);
|
2021-08-04 12:28:33 -06:00
|
|
|
|
_sessions = new ConcurrentDictionary<SoundIoHardwareDeviceSession, byte>();
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
_audioContext.Connect();
|
|
|
|
|
_audioContext.FlushEvents();
|
|
|
|
|
|
2022-12-10 16:57:01 -07:00
|
|
|
|
_audioDevice = FindValidAudioDevice(_audioContext, true);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public static bool IsSupported => IsSupportedInternal();
|
|
|
|
|
|
|
|
|
|
private static bool IsSupportedInternal()
|
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
SoundIoContext context = null;
|
|
|
|
|
SoundIoDeviceContext device = null;
|
|
|
|
|
SoundIoOutStreamContext stream = null;
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
bool backendDisconnected = false;
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
context = SoundIoContext.Create();
|
|
|
|
|
context.OnBackendDisconnect = err =>
|
2021-02-25 17:11:56 -07:00
|
|
|
|
{
|
|
|
|
|
backendDisconnected = true;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
context.Connect();
|
|
|
|
|
context.FlushEvents();
|
|
|
|
|
|
|
|
|
|
if (backendDisconnected)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (context.OutputDeviceCount == 0)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 16:57:01 -07:00
|
|
|
|
device = FindValidAudioDevice(context);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
if (device == null || backendDisconnected)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
stream = device.CreateOutStream();
|
|
|
|
|
|
|
|
|
|
if (stream == null || backendDisconnected)
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
finally
|
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
stream?.Dispose();
|
|
|
|
|
context?.Dispose();
|
2021-02-25 17:11:56 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 16:57:01 -07:00
|
|
|
|
private static SoundIoDeviceContext FindValidAudioDevice(SoundIoContext audioContext, bool fallback = false)
|
2021-02-25 17:11:56 -07:00
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
SoundIoDeviceContext defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
if (!defaultAudioDevice.IsRaw)
|
|
|
|
|
{
|
|
|
|
|
return defaultAudioDevice;
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 16:57:01 -07:00
|
|
|
|
for (int i = 0; i < audioContext.OutputDeviceCount; i++)
|
2021-02-25 17:11:56 -07:00
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
SoundIoDeviceContext audioDevice = audioContext.GetOutputDevice(i);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw)
|
|
|
|
|
{
|
|
|
|
|
return audioDevice;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return fallback ? defaultAudioDevice : null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ManualResetEvent GetUpdateRequiredEvent()
|
|
|
|
|
{
|
|
|
|
|
return _updateRequiredEvent;
|
|
|
|
|
}
|
|
|
|
|
|
2021-09-11 14:08:25 -06:00
|
|
|
|
public ManualResetEvent GetPauseEvent()
|
|
|
|
|
{
|
|
|
|
|
return _pauseEvent;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-23 09:33:56 -07:00
|
|
|
|
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
2021-02-25 17:11:56 -07:00
|
|
|
|
{
|
|
|
|
|
if (channelCount == 0)
|
|
|
|
|
{
|
|
|
|
|
channelCount = 2;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sampleRate == 0)
|
|
|
|
|
{
|
|
|
|
|
sampleRate = Constants.TargetSampleRate;
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-23 09:33:56 -07:00
|
|
|
|
volume = Math.Clamp(volume, 0, 1);
|
|
|
|
|
|
2021-02-25 17:11:56 -07:00
|
|
|
|
if (direction != Direction.Output)
|
|
|
|
|
{
|
|
|
|
|
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-23 09:33:56 -07:00
|
|
|
|
SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
2021-08-04 12:28:33 -06:00
|
|
|
|
_sessions.TryAdd(session, 0);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
2021-08-04 12:28:33 -06:00
|
|
|
|
return session;
|
2021-02-25 17:11:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-08-04 12:28:33 -06:00
|
|
|
|
internal bool Unregister(SoundIoHardwareDeviceSession session)
|
2021-02-25 17:11:56 -07:00
|
|
|
|
{
|
2021-08-04 12:28:33 -06:00
|
|
|
|
return _sessions.TryRemove(session, out _);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 16:57:01 -07:00
|
|
|
|
public static SoundIoFormat GetSoundIoFormat(SampleFormat format)
|
2021-02-25 17:11:56 -07:00
|
|
|
|
{
|
|
|
|
|
return format switch
|
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
SampleFormat.PcmInt8 => SoundIoFormat.S8,
|
|
|
|
|
SampleFormat.PcmInt16 => SoundIoFormat.S16LE,
|
|
|
|
|
SampleFormat.PcmInt24 => SoundIoFormat.S24LE,
|
|
|
|
|
SampleFormat.PcmInt32 => SoundIoFormat.S32LE,
|
|
|
|
|
SampleFormat.PcmFloat => SoundIoFormat.Float32LE,
|
2021-02-25 17:11:56 -07:00
|
|
|
|
_ => throw new ArgumentException ($"Unsupported sample format {format}"),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 16:57:01 -07:00
|
|
|
|
internal SoundIoOutStreamContext OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount)
|
2021-02-25 17:11:56 -07:00
|
|
|
|
{
|
2022-12-10 16:57:01 -07:00
|
|
|
|
SoundIoFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
if (!_audioDevice.SupportsSampleRate((int)requestedSampleRate))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException($"This sound device does not support a sample rate of {requestedSampleRate}Hz");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_audioDevice.SupportsFormat(driverSampleFormat))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException($"This sound device does not support {requestedSampleFormat}");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!_audioDevice.SupportsChannelCount((int)requestedChannelCount))
|
|
|
|
|
{
|
|
|
|
|
throw new ArgumentException($"This sound device does not support channel count {requestedChannelCount}");
|
|
|
|
|
}
|
|
|
|
|
|
2022-12-10 16:57:01 -07:00
|
|
|
|
SoundIoOutStreamContext result = _audioDevice.CreateOutStream();
|
2021-02-25 17:11:56 -07:00
|
|
|
|
|
|
|
|
|
result.Name = "Ryujinx";
|
2022-12-10 16:57:01 -07:00
|
|
|
|
result.Layout = SoundIoChannelLayout.GetDefaultValue((int)requestedChannelCount);
|
2021-02-25 17:11:56 -07:00
|
|
|
|
result.Format = driverSampleFormat;
|
|
|
|
|
result.SampleRate = (int)requestedSampleRate;
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void FlushContextEvents()
|
|
|
|
|
{
|
|
|
|
|
_audioContext.FlushEvents();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
{
|
2021-06-29 11:37:13 -06:00
|
|
|
|
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
|
|
|
|
{
|
|
|
|
|
Dispose(true);
|
|
|
|
|
}
|
2021-02-25 17:11:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
{
|
|
|
|
|
if (disposing)
|
|
|
|
|
{
|
2021-08-04 12:28:33 -06:00
|
|
|
|
foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
|
2021-02-25 17:11:56 -07:00
|
|
|
|
{
|
2021-08-04 12:28:33 -06:00
|
|
|
|
session.Dispose();
|
2021-02-25 17:11:56 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_audioContext.Disconnect();
|
|
|
|
|
_audioContext.Dispose();
|
2021-09-11 14:08:25 -06:00
|
|
|
|
_pauseEvent.Dispose();
|
2021-02-25 17:11:56 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SupportsSampleRate(uint sampleRate)
|
|
|
|
|
{
|
|
|
|
|
return _audioDevice.SupportsSampleRate((int)sampleRate);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
|
|
|
|
{
|
|
|
|
|
return _audioDevice.SupportsFormat(GetSoundIoFormat(sampleFormat));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SupportsChannelCount(uint channelCount)
|
|
|
|
|
{
|
|
|
|
|
return _audioDevice.SupportsChannelCount((int)channelCount);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public bool SupportsDirection(Direction direction)
|
|
|
|
|
{
|
|
|
|
|
// TODO: add direction input when supported.
|
|
|
|
|
return direction == Direction.Output;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|