From eb056218a13fa145adcc9ecafd166b1b1f2caccb Mon Sep 17 00:00:00 2001 From: Mary Date: Wed, 5 May 2021 23:37:09 +0200 Subject: [PATCH] audio: Implement a SDL2 backend (#2258) * audio: Implement a SDL2 backend This adds support to SDL2 as an audio backend. It has the same compatibility level as OpenAL without its issues. I also took the liberty of restructuring the SDL2 code to have one shared project between audio and input. The configuration version was also incremented. * Address gdkchan's comments * Fix update logic * Add an heuristic to pick the correct target sample count wanted by the game * Address gdkchan's comments * Address Ac_k's comments * Fix audren output * Address gdkchan's comments --- .../Ryujinx.Audio.Backends.SDL2.csproj | 13 + .../SDL2AudioBuffer.cs | 16 ++ .../SDL2HardwareDeviceDriver.cs | 184 +++++++++++++++ .../SDL2HardwareDeviceSession.cs | 223 ++++++++++++++++++ .../Common/HardwareDeviceSessionOutputBase.cs | 9 +- Ryujinx.Common/Configuration/AudioBackend.cs | 3 +- .../Configuration/ConfigurationFileFormat.cs | 2 +- .../Configuration/ConfigurationState.cs | 7 + Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj | 5 +- Ryujinx.Input.SDL2/SDL2GamepadDriver.cs | 3 +- .../Ryujinx.SDL2.Common.csproj | 15 ++ .../SDL2Driver.cs | 6 +- Ryujinx.sln | 16 +- Ryujinx/Ryujinx.csproj | 1 + Ryujinx/Ui/MainWindow.cs | 14 +- Ryujinx/Ui/Windows/SettingsWindow.cs | 8 + 16 files changed, 511 insertions(+), 14 deletions(-) create mode 100644 Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj create mode 100644 Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs create mode 100644 Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs create mode 100644 Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs create mode 100644 Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj rename {Ryujinx.Input.SDL2 => Ryujinx.SDL2.Common}/SDL2Driver.cs (97%) diff --git a/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj b/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj new file mode 100644 index 00000000..6619a500 --- /dev/null +++ b/Ryujinx.Audio.Backends.SDL2/Ryujinx.Audio.Backends.SDL2.csproj @@ -0,0 +1,13 @@ + + + + net5.0 + true + + + + + + + + diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs b/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs new file mode 100644 index 00000000..71ef414a --- /dev/null +++ b/Ryujinx.Audio.Backends.SDL2/SDL2AudioBuffer.cs @@ -0,0 +1,16 @@ +namespace Ryujinx.Audio.Backends.SDL2 +{ + class SDL2AudioBuffer + { + public readonly ulong DriverIdentifier; + public readonly ulong SampleCount; + public ulong SamplePlayed; + + public SDL2AudioBuffer(ulong driverIdentifier, ulong sampleCount) + { + DriverIdentifier = driverIdentifier; + SampleCount = sampleCount; + SamplePlayed = 0; + } + } +} diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs new file mode 100644 index 00000000..07131d1d --- /dev/null +++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceDriver.cs @@ -0,0 +1,184 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using Ryujinx.SDL2.Common; +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Threading; + +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; +using static SDL2.SDL; + +namespace Ryujinx.Audio.Backends.SDL2 +{ + public class SDL2HardwareDeviceDriver : IHardwareDeviceDriver + { + private object _lock = new object(); + + private ManualResetEvent _updateRequiredEvent; + private List _sessions; + + public SDL2HardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + _sessions = new List(); + + SDL2Driver.Instance.Initialize(); + } + + public static bool IsSupported => IsSupportedInternal(); + + private static bool IsSupportedInternal() + { + uint device = OpenStream(SampleFormat.PcmInt16, Constants.TargetSampleRate, Constants.ChannelCountMax, Constants.TargetSampleCount, null); + + if (device != 0) + { + SDL_CloseAudioDevice(device); + } + + return device != 0; + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + { + if (channelCount == 0) + { + channelCount = 2; + } + + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (direction != Direction.Output) + { + throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!"); + } + + lock (_lock) + { + SDL2HardwareDeviceSession session = new SDL2HardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount); + + _sessions.Add(session); + + return session; + } + } + + internal void Unregister(SDL2HardwareDeviceSession session) + { + lock (_lock) + { + _sessions.Remove(session); + } + } + + private static SDL_AudioSpec GetSDL2Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount) + { + return new SDL_AudioSpec + { + channels = (byte)requestedChannelCount, + format = GetSDL2Format(requestedSampleFormat), + freq = (int)requestedSampleRate, + samples = (ushort)sampleCount + }; + } + + internal static ushort GetSDL2Format(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => AUDIO_S8, + SampleFormat.PcmInt16 => AUDIO_S16, + SampleFormat.PcmInt32 => AUDIO_S32, + SampleFormat.PcmFloat => AUDIO_F32, + _ => throw new ArgumentException($"Unsupported sample format {format}"), + }; + } + + // TODO: Fix this in SDL2-CS. + [DllImport("SDL2", EntryPoint = "SDL_OpenAudioDevice", CallingConvention = CallingConvention.Cdecl)] + private static extern uint SDL_OpenAudioDevice_Workaround( + IntPtr name, + int iscapture, + ref SDL_AudioSpec desired, + out SDL_AudioSpec obtained, + uint allowed_changes + ); + + internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback) + { + SDL_AudioSpec desired = GetSDL2Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount); + + desired.callback = callback; + + uint device = SDL_OpenAudioDevice_Workaround(IntPtr.Zero, 0, ref desired, out SDL_AudioSpec got, 0); + + if (device == 0) + { + return 0; + } + + bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels; + + if (!isValid) + { + SDL_CloseAudioDevice(device); + + return 0; + } + + return device; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + while (_sessions.Count > 0) + { + SDL2HardwareDeviceSession session = _sessions[_sessions.Count - 1]; + + session.Dispose(); + } + + SDL2Driver.Instance.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return sampleFormat != SampleFormat.PcmInt24; + } + + public bool SupportsChannelCount(uint channelCount) + { + return true; + } + + public bool SupportsDirection(Direction direction) + { + // TODO: add direction input when supported. + return direction == Direction.Output; + } + } +} diff --git a/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs new file mode 100644 index 00000000..344dd9b6 --- /dev/null +++ b/Ryujinx.Audio.Backends.SDL2/SDL2HardwareDeviceSession.cs @@ -0,0 +1,223 @@ +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using System.Threading; + +using static SDL2.SDL; + +namespace Ryujinx.Audio.Backends.SDL2 +{ + class SDL2HardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private SDL2HardwareDeviceDriver _driver; + private ConcurrentQueue _queuedBuffers; + private DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private ManualResetEvent _updateRequiredEvent; + private uint _outputStream; + private SDL_AudioCallback _callbackDelegate; + private int _bytesPerFrame; + private uint _sampleCount; + private bool _started; + private float _volume; + private ushort _nativeSampleFormat; + + public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); + _queuedBuffers = new ConcurrentQueue(); + _ringBuffer = new DynamicRingBuffer(); + _callbackDelegate = Update; + _bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount; + _nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat); + _sampleCount = uint.MaxValue; + _started = false; + _volume = 1.0f; + } + + private void EnsureAudioStreamSetup(AudioBuffer buffer) + { + bool needAudioSetup = _outputStream == 0 || ((uint)GetSampleCount(buffer) % _sampleCount) != 0; + + if (needAudioSetup) + { + _sampleCount = Math.Max(Constants.TargetSampleCount, (uint)GetSampleCount(buffer)); + + uint newOutputStream = SDL2HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate); + + if (newOutputStream == 0) + { + // No stream in place, this is unexpected. + throw new InvalidOperationException($"OpenStream failed with error: \"{SDL_GetError()}\""); + } + else + { + if (_outputStream != 0) + { + SDL_CloseAudioDevice(_outputStream); + } + + _outputStream = newOutputStream; + + SDL_PauseAudioDevice(_outputStream, _started ? 0 : 1); + + Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}"); + } + } + } + + // TODO: Add this variant with pointer to SDL2-CS. + [DllImport("SDL2", EntryPoint = "SDL_MixAudioFormat", CallingConvention = CallingConvention.Cdecl)] + private static extern unsafe uint SDL_MixAudioFormat(IntPtr dst, IntPtr src, ushort format, uint len, int volume); + + private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength) + { + Span streamSpan = new Span((void*)stream, streamLength); + + int maxFrameCount = (int)GetSampleCount(streamLength); + int bufferedFrames = _ringBuffer.Length / _bytesPerFrame; + + int frameCount = Math.Min(bufferedFrames, maxFrameCount); + + if (frameCount == 0) + { + // SDL2 left the responsability to the user to clear the buffer. + streamSpan.Fill(0); + + return; + } + + byte[] samples = new byte[frameCount * _bytesPerFrame]; + + _ringBuffer.Read(samples, 0, samples.Length); + + samples.AsSpan().CopyTo(streamSpan); + streamSpan.Slice(samples.Length).Fill(0); + + // Apply volume to written data + SDL_MixAudioFormat(stream, stream, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME)); + + ulong sampleCount = GetSampleCount(samples.Length); + + ulong availaibleSampleCount = sampleCount; + + bool needUpdate = false; + + while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) + { + ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed); + ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); + + ulong currentSamplePlayed = Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount); + availaibleSampleCount -= playedAudioBufferSampleCount; + + if (currentSamplePlayed == driverBuffer.SampleCount) + { + _queuedBuffers.TryDequeue(out _); + + needUpdate = true; + } + + Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount); + } + + // Notify the output if needed. + if (needUpdate) + { + _updateRequiredEvent.Set(); + } + } + + public override ulong GetPlayedSampleCount() + { + return Interlocked.Read(ref _playedSampleCount); + } + + public override float GetVolume() + { + return _volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + EnsureAudioStreamSetup(buffer); + + SDL2AudioBuffer driverBuffer = new SDL2AudioBuffer(buffer.DataPointer, GetSampleCount(buffer)); + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + + public override void SetVolume(float volume) + { + _volume = volume; + } + + public override void Start() + { + if (!_started) + { + if (_outputStream != 0) + { + SDL_PauseAudioDevice(_outputStream, 0); + } + + _started = true; + } + } + + public override void Stop() + { + if (_started) + { + if (_outputStream != 0) + { + SDL_PauseAudioDevice(_outputStream, 1); + } + + _started = false; + } + } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + if (!_queuedBuffers.TryPeek(out SDL2AudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + PrepareToClose(); + Stop(); + + if (_outputStream != 0) + { + SDL_CloseAudioDevice(_outputStream); + } + + _driver.Unregister(this); + } + } + + public override void Dispose() + { + Dispose(true); + } + } +} diff --git a/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs b/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs index 1e000e4c..f1f0039c 100644 --- a/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs +++ b/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs @@ -18,6 +18,7 @@ using Ryujinx.Audio.Common; using Ryujinx.Audio.Integration; using Ryujinx.Memory; +using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Backends.Common { @@ -52,7 +53,13 @@ namespace Ryujinx.Audio.Backends.Common protected ulong GetSampleCount(AudioBuffer buffer) { - return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, (int)buffer.DataSize); + return GetSampleCount((int)buffer.DataSize); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected ulong GetSampleCount(int dataSize) + { + return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, dataSize); } public abstract void Dispose(); diff --git a/Ryujinx.Common/Configuration/AudioBackend.cs b/Ryujinx.Common/Configuration/AudioBackend.cs index 28233354..e42df039 100644 --- a/Ryujinx.Common/Configuration/AudioBackend.cs +++ b/Ryujinx.Common/Configuration/AudioBackend.cs @@ -4,6 +4,7 @@ { Dummy, OpenAl, - SoundIo + SoundIo, + SDL2 } } \ No newline at end of file diff --git a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs index 092bb9dd..be9c6864 100644 --- a/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs +++ b/Ryujinx.Common/Configuration/ConfigurationFileFormat.cs @@ -14,7 +14,7 @@ namespace Ryujinx.Configuration /// /// The current version of the file format /// - public const int CurrentVersion = 24; + public const int CurrentVersion = 25; public int Version { get; set; } diff --git a/Ryujinx.Common/Configuration/ConfigurationState.cs b/Ryujinx.Common/Configuration/ConfigurationState.cs index e65bcfcb..9ea5c282 100644 --- a/Ryujinx.Common/Configuration/ConfigurationState.cs +++ b/Ryujinx.Common/Configuration/ConfigurationState.cs @@ -803,6 +803,13 @@ namespace Ryujinx.Configuration configurationFileUpdated = true; } + if (configurationFileFormat.Version < 25) + { + Common.Logging.Logger.Warning?.Print(LogClass.Application, $"Outdated configuration version {configurationFileFormat.Version}, migrating to version 25."); + + configurationFileUpdated = true; + } + Logger.EnableFileLog.Value = configurationFileFormat.EnableFileLog; Graphics.ResScale.Value = configurationFileFormat.ResScale; Graphics.ResScaleCustom.Value = configurationFileFormat.ResScaleCustom; diff --git a/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj b/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj index cee18996..2d61dfb8 100644 --- a/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj +++ b/Ryujinx.Input.SDL2/Ryujinx.Input.SDL2.csproj @@ -5,12 +5,9 @@ true - - - - + diff --git a/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs index 62098383..927d7fe6 100644 --- a/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs +++ b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs @@ -1,4 +1,5 @@ -using System; +using Ryujinx.SDL2.Common; +using System; using System.Collections.Generic; using static SDL2.SDL; diff --git a/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj b/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj new file mode 100644 index 00000000..a35f8743 --- /dev/null +++ b/Ryujinx.SDL2.Common/Ryujinx.SDL2.Common.csproj @@ -0,0 +1,15 @@ + + + + net5.0 + + + + + + + + + + + diff --git a/Ryujinx.Input.SDL2/SDL2Driver.cs b/Ryujinx.SDL2.Common/SDL2Driver.cs similarity index 97% rename from Ryujinx.Input.SDL2/SDL2Driver.cs rename to Ryujinx.SDL2.Common/SDL2Driver.cs index f77bb1d5..edd634ee 100644 --- a/Ryujinx.Input.SDL2/SDL2Driver.cs +++ b/Ryujinx.SDL2.Common/SDL2Driver.cs @@ -4,9 +4,9 @@ using System.IO; using System.Threading; using static SDL2.SDL; -namespace Ryujinx.Input.SDL2 +namespace Ryujinx.SDL2.Common { - class SDL2Driver : IDisposable + public class SDL2Driver : IDisposable { private static SDL2Driver _instance; @@ -25,7 +25,7 @@ namespace Ryujinx.Input.SDL2 } } - private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK; + private const uint SdlInitFlags = SDL_INIT_EVENTS | SDL_INIT_GAMECONTROLLER | SDL_INIT_JOYSTICK | SDL_INIT_AUDIO; private bool _isRunning; private uint _refereceCount; diff --git a/Ryujinx.sln b/Ryujinx.sln index bd00f000..f4eec573 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -59,9 +59,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.Open EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.SoundIo", "Ryujinx.Audio.Backends.SoundIo\Ryujinx.Audio.Backends.SoundIo.csproj", "{716364DE-B988-41A6-BAB4-327964266ECC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input", "Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input", "Ryujinx.Input\Ryujinx.Input.csproj", "{C16F112F-38C3-40BC-9F5F-4791112063D6}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Input.SDL2", "Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Input.SDL2", "Ryujinx.Input.SDL2\Ryujinx.Input.SDL2.csproj", "{DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.SDL2.Common", "Ryujinx.SDL2.Common\Ryujinx.SDL2.Common.csproj", "{2D5D3A1D-5730-4648-B0AB-06C53CB910C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ryujinx.Audio.Backends.SDL2", "Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj", "{D99A395A-8569-4DB0-B336-900647890052}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -177,6 +181,14 @@ Global {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Debug|Any CPU.Build.0 = Debug|Any CPU {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.ActiveCfg = Release|Any CPU {DFAB6F2D-B9BF-4AFF-B22B-7684A328EBA3}.Release|Any CPU.Build.0 = Release|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D5D3A1D-5730-4648-B0AB-06C53CB910C0}.Release|Any CPU.Build.0 = Release|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D99A395A-8569-4DB0-B336-900647890052}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Ryujinx/Ryujinx.csproj b/Ryujinx/Ryujinx.csproj index 6a441a61..1cd9595b 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -26,6 +26,7 @@ + diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 56dcf3eb..a2a00992 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -5,6 +5,7 @@ using LibHac.Common; using LibHac.Ns; using Ryujinx.Audio.Backends.Dummy; using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Audio.Integration; using Ryujinx.Common.Configuration; @@ -327,7 +328,18 @@ namespace Ryujinx.Ui IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver(); - if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) + if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SDL2) + { + if (SDL2HardwareDeviceDriver.IsSupported) + { + deviceDriver = new SDL2HardwareDeviceDriver(); + } + else + { + Logger.Warning?.Print(LogClass.Audio, "SDL2 audio is not supported, falling back to dummy audio out."); + } + } + else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) { if (SoundIoHardwareDeviceDriver.IsSupported) { diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs index be9dc271..94afb242 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/Ryujinx/Ui/Windows/SettingsWindow.cs @@ -1,5 +1,6 @@ using Gtk; using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SDL2; using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; @@ -302,6 +303,7 @@ namespace Ryujinx.Ui.Windows TreeIter openAlIter = _audioBackendStore.AppendValues("OpenAL", AudioBackend.OpenAl); TreeIter soundIoIter = _audioBackendStore.AppendValues("SoundIO", AudioBackend.SoundIo); + TreeIter sdl2Iter = _audioBackendStore.AppendValues("SDL2", AudioBackend.SDL2); TreeIter dummyIter = _audioBackendStore.AppendValues("Dummy", AudioBackend.Dummy); _audioBackendSelect = ComboBox.NewWithModelAndEntry(_audioBackendStore); @@ -316,6 +318,9 @@ namespace Ryujinx.Ui.Windows case AudioBackend.SoundIo: _audioBackendSelect.SetActiveIter(soundIoIter); break; + case AudioBackend.SDL2: + _audioBackendSelect.SetActiveIter(sdl2Iter); + break; case AudioBackend.Dummy: _audioBackendSelect.SetActiveIter(dummyIter); break; @@ -328,11 +333,13 @@ namespace Ryujinx.Ui.Windows bool openAlIsSupported = false; bool soundIoIsSupported = false; + bool sdl2IsSupported = false; Task.Run(() => { openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported; soundIoIsSupported = SoundIoHardwareDeviceDriver.IsSupported; + sdl2IsSupported = SDL2HardwareDeviceDriver.IsSupported; }); // This function runs whenever the dropdown is opened @@ -342,6 +349,7 @@ namespace Ryujinx.Ui.Windows { AudioBackend.OpenAl => openAlIsSupported, AudioBackend.SoundIo => soundIoIsSupported, + AudioBackend.SDL2 => sdl2IsSupported, AudioBackend.Dummy => true, _ => throw new ArgumentOutOfRangeException() };