update
This commit is contained in:
parent
91f70584ec
commit
241b0152ad
@ -1,208 +0,0 @@
|
||||
// using Ryujinx.Audio.Common;
|
||||
// using Ryujinx.Audio.Integration;
|
||||
// using Ryujinx.Common.Logging;
|
||||
// using Ryujinx.Memory;
|
||||
// using Ryujinx.SDL3.Common;
|
||||
// using System;
|
||||
// using System.Collections.Concurrent;
|
||||
// using System.Runtime.InteropServices;
|
||||
// using System.Threading;
|
||||
// using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
// using static SDL3.SDL;
|
||||
//
|
||||
// namespace Ryujinx.Audio.Backends.SDL3
|
||||
// {
|
||||
// public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
|
||||
// {
|
||||
// private readonly ManualResetEvent _updateRequiredEvent;
|
||||
// private readonly ManualResetEvent _pauseEvent;
|
||||
// private readonly ConcurrentDictionary<SDL3HardwareDeviceSession, byte> _sessions;
|
||||
//
|
||||
// private readonly bool _supportSurroundConfiguration;
|
||||
//
|
||||
// public float Volume { get; set; }
|
||||
//
|
||||
// // TODO: Add this to SDL3-CS
|
||||
// // NOTE: We use a DllImport here because of marshaling issue for spec.
|
||||
// #pragma warning disable SYSLIB1054
|
||||
// [DllImport("SDL3")]
|
||||
// private static extern int SDL_GetDefaultAudioInfo(nint name, out SDL_AudioSpec spec, int isCapture);
|
||||
// #pragma warning restore SYSLIB1054
|
||||
//
|
||||
// public SDL3HardwareDeviceDriver()
|
||||
// {
|
||||
// _updateRequiredEvent = new ManualResetEvent(false);
|
||||
// _pauseEvent = new ManualResetEvent(true);
|
||||
// _sessions = new ConcurrentDictionary<SDL3HardwareDeviceSession, byte>();
|
||||
//
|
||||
// SDL3Driver.Instance.Initialize();
|
||||
//
|
||||
// int res = SDL_GetDefaultAudioInfo(nint.Zero, out var spec, 0);
|
||||
//
|
||||
// if (res != 0)
|
||||
// {
|
||||
// Logger.Error?.Print(LogClass.Application,
|
||||
// $"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\"");
|
||||
//
|
||||
// _supportSurroundConfiguration = true;
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// _supportSurroundConfiguration = spec.channels >= 6;
|
||||
// }
|
||||
//
|
||||
// Volume = 1f;
|
||||
// }
|
||||
//
|
||||
// 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 ManualResetEvent GetPauseEvent()
|
||||
// {
|
||||
// return _pauseEvent;
|
||||
// }
|
||||
//
|
||||
// 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 SDL3 backend!");
|
||||
// }
|
||||
//
|
||||
// SDL3HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
//
|
||||
// _sessions.TryAdd(session, 0);
|
||||
//
|
||||
// return session;
|
||||
// }
|
||||
//
|
||||
// internal bool Unregister(SDL3HardwareDeviceSession session)
|
||||
// {
|
||||
// return _sessions.TryRemove(session, out _);
|
||||
// }
|
||||
//
|
||||
// private static SDL_AudioSpec GetSDL3Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount)
|
||||
// {
|
||||
// return new SDL_AudioSpec
|
||||
// {
|
||||
// channels = (byte)requestedChannelCount,
|
||||
// format = GetSDL3Format(requestedSampleFormat),
|
||||
// freq = (int)requestedSampleRate,
|
||||
// samples = (ushort)sampleCount,
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// internal static ushort GetSDL3Format(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}"),
|
||||
// };
|
||||
// }
|
||||
//
|
||||
// internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, uint sampleCount, SDL_AudioCallback callback)
|
||||
// {
|
||||
// SDL_AudioSpec desired = GetSDL3Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount, sampleCount);
|
||||
//
|
||||
// desired.callback = callback;
|
||||
//
|
||||
// uint device = SDL_OpenAudioDevice(nint.Zero, 0, ref desired, out SDL_AudioSpec got, 0);
|
||||
//
|
||||
// if (device == 0)
|
||||
// {
|
||||
// Logger.Error?.Print(LogClass.Application, $"SDL3 open audio device initialization failed with error \"{SDL_GetError()}\"");
|
||||
//
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// bool isValid = got.format == desired.format && got.freq == desired.freq && got.channels == desired.channels;
|
||||
//
|
||||
// if (!isValid)
|
||||
// {
|
||||
// Logger.Error?.Print(LogClass.Application, "SDL3 open audio device is not valid");
|
||||
// SDL_CloseAudioDevice(device);
|
||||
//
|
||||
// return 0;
|
||||
// }
|
||||
//
|
||||
// return device;
|
||||
// }
|
||||
//
|
||||
// public void Dispose()
|
||||
// {
|
||||
// GC.SuppressFinalize(this);
|
||||
// Dispose(true);
|
||||
// }
|
||||
//
|
||||
// protected virtual void Dispose(bool disposing)
|
||||
// {
|
||||
// if (disposing)
|
||||
// {
|
||||
// foreach (SDL3HardwareDeviceSession session in _sessions.Keys)
|
||||
// {
|
||||
// session.Dispose();
|
||||
// }
|
||||
//
|
||||
// SDL3Driver.Instance.Dispose();
|
||||
//
|
||||
// _pauseEvent.Dispose();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public bool SupportsSampleRate(uint sampleRate)
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
||||
// {
|
||||
// return sampleFormat != SampleFormat.PcmInt24;
|
||||
// }
|
||||
//
|
||||
// public bool SupportsChannelCount(uint channelCount)
|
||||
// {
|
||||
// if (channelCount == 6)
|
||||
// {
|
||||
// return _supportSurroundConfiguration;
|
||||
// }
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// public bool SupportsDirection(Direction direction)
|
||||
// {
|
||||
// // TODO: add direction input when supported.
|
||||
// return direction == Direction.Output;
|
||||
// }
|
||||
// }
|
||||
// }
|
@ -1,234 +0,0 @@
|
||||
// using Ryujinx.Audio.Backends.Common;
|
||||
// using Ryujinx.Audio.Common;
|
||||
// using Ryujinx.Common.Logging;
|
||||
// using Ryujinx.Common.Memory;
|
||||
// using Ryujinx.Memory;
|
||||
// using System;
|
||||
// using System.Buffers;
|
||||
// using System.Collections.Concurrent;
|
||||
// using System.Threading;
|
||||
//
|
||||
// using static SDL3.SDL;
|
||||
//
|
||||
// namespace Ryujinx.Audio.Backends.SDL3
|
||||
// {
|
||||
// class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
// {
|
||||
// private readonly SDL3HardwareDeviceDriver _driver;
|
||||
// private readonly ConcurrentQueue<SDL3AudioBuffer> _queuedBuffers;
|
||||
// private readonly DynamicRingBuffer _ringBuffer;
|
||||
// private ulong _playedSampleCount;
|
||||
// private readonly ManualResetEvent _updateRequiredEvent;
|
||||
// private uint _outputStream;
|
||||
// private bool _hasSetupError;
|
||||
// private readonly SDL_AudioCallback _callbackDelegate;
|
||||
// private readonly int _bytesPerFrame;
|
||||
// private uint _sampleCount;
|
||||
// private bool _started;
|
||||
// private float _volume;
|
||||
// private readonly ushort _nativeSampleFormat;
|
||||
//
|
||||
// public SDL3HardwareDeviceSession(SDL3HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
// {
|
||||
// _driver = driver;
|
||||
// _updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
// _queuedBuffers = new ConcurrentQueue<SDL3AudioBuffer>();
|
||||
// _ringBuffer = new DynamicRingBuffer();
|
||||
// _callbackDelegate = Update;
|
||||
// _bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount;
|
||||
// _nativeSampleFormat = SDL3HardwareDeviceDriver.GetSDL3Format(RequestedSampleFormat);
|
||||
// _sampleCount = uint.MaxValue;
|
||||
// _started = false;
|
||||
// _volume = 1f;
|
||||
// }
|
||||
//
|
||||
// private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||
// {
|
||||
// uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||
// bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
|
||||
// (bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||
//
|
||||
// if (needAudioSetup)
|
||||
// {
|
||||
// _sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount);
|
||||
//
|
||||
// uint newOutputStream = SDL3HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||
//
|
||||
// _hasSetupError = newOutputStream == 0;
|
||||
//
|
||||
// if (!_hasSetupError)
|
||||
// {
|
||||
// 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}");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// private unsafe void Update(nint userdata, nint stream, int streamLength)
|
||||
// {
|
||||
// Span<byte> streamSpan = new((void*)stream, streamLength);
|
||||
//
|
||||
// int maxFrameCount = (int)GetSampleCount(streamLength);
|
||||
// int bufferedFrames = _ringBuffer.Length / _bytesPerFrame;
|
||||
//
|
||||
// int frameCount = Math.Min(bufferedFrames, maxFrameCount);
|
||||
//
|
||||
// if (frameCount == 0)
|
||||
// {
|
||||
// // SDL3 left the responsibility to the user to clear the buffer.
|
||||
// streamSpan.Clear();
|
||||
//
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame);
|
||||
//
|
||||
// Span<byte> samples = samplesOwner.Span;
|
||||
//
|
||||
// _ringBuffer.Read(samples, 0, samples.Length);
|
||||
//
|
||||
// fixed (byte* p = samples)
|
||||
// {
|
||||
// nint pStreamSrc = (nint)p;
|
||||
//
|
||||
// // Zero the dest buffer
|
||||
// streamSpan.Clear();
|
||||
//
|
||||
// // Apply volume to written data
|
||||
// SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
|
||||
// }
|
||||
//
|
||||
// ulong sampleCount = GetSampleCount(samples.Length);
|
||||
//
|
||||
// ulong availaibleSampleCount = sampleCount;
|
||||
//
|
||||
// bool needUpdate = false;
|
||||
//
|
||||
// while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL3AudioBuffer 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);
|
||||
//
|
||||
// if (_outputStream != 0)
|
||||
// {
|
||||
// SDL3AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
|
||||
//
|
||||
// _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
//
|
||||
// _queuedBuffers.Enqueue(driverBuffer);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
// Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer));
|
||||
//
|
||||
// _updateRequiredEvent.Set();
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// 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 SDL3AudioBuffer driverBuffer))
|
||||
// {
|
||||
// return true;
|
||||
// }
|
||||
//
|
||||
// return driverBuffer.DriverIdentifier != buffer.DataPointer;
|
||||
// }
|
||||
//
|
||||
// protected virtual void Dispose(bool disposing)
|
||||
// {
|
||||
// if (disposing && _driver.Unregister(this))
|
||||
// {
|
||||
// PrepareToClose();
|
||||
// Stop();
|
||||
//
|
||||
// if (_outputStream != 0)
|
||||
// {
|
||||
// SDL_CloseAudioDevice(_outputStream);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// public override void Dispose()
|
||||
// {
|
||||
// Dispose(true);
|
||||
// }
|
||||
// }
|
||||
// }
|
217
src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceDriver.cs
Normal file
217
src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceDriver.cs
Normal file
@ -0,0 +1,217 @@
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Memory;
|
||||
using Ryujinx.SDL3.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
using static SDL3.SDL;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SDL3
|
||||
{
|
||||
public class SDL3HardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private readonly ManualResetEvent _pauseEvent;
|
||||
private readonly ConcurrentDictionary<SDL3HardwareDeviceSession, byte> _sessions;
|
||||
|
||||
private readonly bool _supportSurroundConfiguration;
|
||||
|
||||
public float Volume { get; set; }
|
||||
|
||||
public SDL3HardwareDeviceDriver()
|
||||
{
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
_sessions = new ConcurrentDictionary<SDL3HardwareDeviceSession, byte>();
|
||||
|
||||
SDL3Driver.Instance.Initialize();
|
||||
|
||||
if (!SDL_GetAudioDeviceFormat(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, out var spec, out int sample_frames))
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"SDL_GetDefaultAudioInfo failed with error \"{SDL_GetError()}\"");
|
||||
|
||||
_supportSurroundConfiguration = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
_supportSurroundConfiguration = spec.channels >= 6;
|
||||
}
|
||||
|
||||
Volume = 1f;
|
||||
}
|
||||
|
||||
public static bool IsSupported => IsSupportedInternal();
|
||||
|
||||
private static bool IsSupportedInternal()
|
||||
{
|
||||
var devices = SDL_GetAudioPlaybackDevices(out int count);
|
||||
var joystickIDs = new int[count];
|
||||
Marshal.Copy(devices, joystickIDs, 0, count);
|
||||
for (int i = 0; i < count; ++i)
|
||||
{
|
||||
Console.WriteLine(joystickIDs[i]);
|
||||
}
|
||||
|
||||
var 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 ManualResetEvent GetPauseEvent()
|
||||
{
|
||||
return _pauseEvent;
|
||||
}
|
||||
|
||||
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 SDL3 backend!");
|
||||
}
|
||||
|
||||
SDL3HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
|
||||
|
||||
_sessions.TryAdd(session, 0);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
internal bool Unregister(SDL3HardwareDeviceSession session)
|
||||
{
|
||||
return _sessions.TryRemove(session, out _);
|
||||
}
|
||||
|
||||
private static SDL_AudioSpec GetSDL3Spec(SampleFormat requestedSampleFormat, uint requestedSampleRate,
|
||||
uint requestedChannelCount, uint sampleCount)
|
||||
{
|
||||
return new SDL_AudioSpec
|
||||
{
|
||||
channels = (byte)requestedChannelCount,
|
||||
format = GetSDL3Format(requestedSampleFormat),
|
||||
freq = (int)requestedSampleRate,
|
||||
};
|
||||
}
|
||||
|
||||
internal static SDL_AudioFormat GetSDL3Format(SampleFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
SampleFormat.PcmInt8 => SDL_AudioFormat.SDL_AUDIO_S8,
|
||||
SampleFormat.PcmInt16 => SDL_AudioFormat.SDL_AUDIO_S16,
|
||||
SampleFormat.PcmInt32 => SDL_AudioFormat.SDL_AUDIO_S32,
|
||||
SampleFormat.PcmFloat => SDL_AudioFormat.SDL_AUDIO_F32,
|
||||
_ => throw new ArgumentException($"Unsupported sample format {format}"),
|
||||
};
|
||||
}
|
||||
|
||||
internal static uint OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate,
|
||||
uint requestedChannelCount, uint sampleCount, SDL_AudioStreamCallback callback)
|
||||
{
|
||||
SDL_AudioSpec desired = GetSDL3Spec(requestedSampleFormat, requestedSampleRate, requestedChannelCount,
|
||||
sampleCount);
|
||||
SDL_AudioSpec desired2 = default;
|
||||
var device = SDL_OpenAudioDevice(SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, ref desired);
|
||||
|
||||
if (device == 0)
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application,
|
||||
$"SDL3 open audio device initialization failed with error \"{SDL_GetError()}\"");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
Console.WriteLine(SDL_GetAudioDeviceName(device));
|
||||
bool isValid = false;
|
||||
if (SDL_GetAudioDeviceFormat(device, out SDL_AudioSpec got, out int i))
|
||||
{
|
||||
Console.WriteLine(got.freq);
|
||||
isValid = got.format == desired.format && got.freq == desired.freq &&
|
||||
got.channels == desired.channels;
|
||||
}
|
||||
|
||||
// if (!isValid)
|
||||
// {
|
||||
// Logger.Error?.Print(LogClass.Application, "SDL3 open audio device is not valid");
|
||||
// SDL_CloseAudioDevice(device);
|
||||
//
|
||||
// return 0;
|
||||
// }
|
||||
|
||||
return device;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
GC.SuppressFinalize(this);
|
||||
Dispose(true);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (SDL3HardwareDeviceSession session in _sessions.Keys)
|
||||
{
|
||||
session.Dispose();
|
||||
}
|
||||
|
||||
SDL3Driver.Instance.Dispose();
|
||||
|
||||
_pauseEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsSampleRate(uint sampleRate)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
||||
{
|
||||
return sampleFormat != SampleFormat.PcmInt24;
|
||||
}
|
||||
|
||||
public bool SupportsChannelCount(uint channelCount)
|
||||
{
|
||||
if (channelCount == 6)
|
||||
{
|
||||
return _supportSurroundConfiguration;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool SupportsDirection(Direction direction)
|
||||
{
|
||||
// TODO: add direction input when supported.
|
||||
return direction == Direction.Output;
|
||||
}
|
||||
}
|
||||
}
|
235
src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceSession.cs
Normal file
235
src/Ryujinx.Audio.Backends.SDL3/SDL3HardwareDeviceSession.cs
Normal file
@ -0,0 +1,235 @@
|
||||
using Ryujinx.Audio.Backends.Common;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Common.Logging;
|
||||
using Ryujinx.Common.Memory;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
using static SDL3.SDL;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SDL3
|
||||
{
|
||||
class SDL3HardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private readonly SDL3HardwareDeviceDriver _driver;
|
||||
private readonly ConcurrentQueue<SDL3AudioBuffer> _queuedBuffers;
|
||||
private readonly DynamicRingBuffer _ringBuffer;
|
||||
private ulong _playedSampleCount;
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private uint _outputStream;
|
||||
private bool _hasSetupError;
|
||||
private readonly SDL_AudioStreamCallback _callbackDelegate;
|
||||
private readonly int _bytesPerFrame;
|
||||
private uint _sampleCount;
|
||||
private bool _started;
|
||||
private float _volume;
|
||||
private readonly SDL_AudioFormat _nativeSampleFormat;
|
||||
|
||||
public SDL3HardwareDeviceSession(SDL3HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
_driver = driver;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
_queuedBuffers = new ConcurrentQueue<SDL3AudioBuffer>();
|
||||
_ringBuffer = new DynamicRingBuffer();
|
||||
_callbackDelegate = Update;
|
||||
_bytesPerFrame = BackendHelper.GetSampleSize(RequestedSampleFormat) * (int)RequestedChannelCount;
|
||||
_nativeSampleFormat = SDL3HardwareDeviceDriver.GetSDL3Format(RequestedSampleFormat);
|
||||
_sampleCount = uint.MaxValue;
|
||||
_started = false;
|
||||
_volume = 1f;
|
||||
}
|
||||
|
||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||
{
|
||||
uint bufferSampleCount = (uint)GetSampleCount(buffer);
|
||||
bool needAudioSetup = (_outputStream == 0 && !_hasSetupError) ||
|
||||
(bufferSampleCount >= Constants.TargetSampleCount && bufferSampleCount < _sampleCount);
|
||||
|
||||
if (needAudioSetup)
|
||||
{
|
||||
_sampleCount = Math.Max(Constants.TargetSampleCount, bufferSampleCount);
|
||||
|
||||
var newOutputStream = SDL3HardwareDeviceDriver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount, _sampleCount, _callbackDelegate);
|
||||
|
||||
_hasSetupError = newOutputStream == 0;
|
||||
|
||||
if (!_hasSetupError)
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL_CloseAudioDevice(_outputStream);
|
||||
}
|
||||
|
||||
_outputStream = newOutputStream;
|
||||
SDL_PauseAudioDevice(_outputStream);
|
||||
|
||||
Logger.Info?.Print(LogClass.Audio, $"New audio stream setup with a target sample count of {_sampleCount}");
|
||||
}
|
||||
}
|
||||
}
|
||||
private unsafe void Update(IntPtr userdata, IntPtr stream, int streamLength, int total_amount)
|
||||
{
|
||||
Console.WriteLine("call");
|
||||
Console.WriteLine(SDL_GetAudioDeviceName(SDL_GetAudioStreamDevice(stream)));
|
||||
Span<byte> streamSpan = new((void*)stream, streamLength);
|
||||
|
||||
int maxFrameCount = (int)GetSampleCount(streamLength);
|
||||
int bufferedFrames = _ringBuffer.Length / _bytesPerFrame;
|
||||
|
||||
int frameCount = Math.Min(bufferedFrames, maxFrameCount);
|
||||
|
||||
if (frameCount == 0)
|
||||
{
|
||||
// SDL3 left the responsibility to the user to clear the buffer.
|
||||
streamSpan.Clear();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
using SpanOwner<byte> samplesOwner = SpanOwner<byte>.Rent(frameCount * _bytesPerFrame);
|
||||
|
||||
Span<byte> samples = samplesOwner.Span;
|
||||
|
||||
_ringBuffer.Read(samples, 0, samples.Length);
|
||||
|
||||
fixed (byte* p = samples)
|
||||
{
|
||||
nint pStreamSrc = (nint)p;
|
||||
|
||||
// Zero the dest buffer
|
||||
streamSpan.Clear();
|
||||
|
||||
// Apply volume to written data
|
||||
// SDL_MixAudio(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, _driver.Volume * _volume);
|
||||
}
|
||||
|
||||
ulong sampleCount = GetSampleCount(samples.Length);
|
||||
|
||||
ulong availaibleSampleCount = sampleCount;
|
||||
|
||||
bool needUpdate = false;
|
||||
|
||||
while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SDL3AudioBuffer 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)
|
||||
{
|
||||
Console.WriteLine(buffer);
|
||||
EnsureAudioStreamSetup(buffer);
|
||||
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL3AudioBuffer driverBuffer = new(buffer.DataPointer, GetSampleCount(buffer));
|
||||
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer));
|
||||
|
||||
_updateRequiredEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
_volume = volume;
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
if (!_started)
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL_ResumeAudioDevice(_outputStream);
|
||||
}
|
||||
|
||||
_started = true;
|
||||
}
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
if (_started)
|
||||
{
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL_PauseAudioDevice(_outputStream);
|
||||
}
|
||||
|
||||
_started = false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void UnregisterBuffer(AudioBuffer buffer) { }
|
||||
|
||||
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
|
||||
{
|
||||
if (!_queuedBuffers.TryPeek(out SDL3AudioBuffer driverBuffer))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return driverBuffer.DriverIdentifier != buffer.DataPointer;
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _driver.Unregister(this))
|
||||
{
|
||||
PrepareToClose();
|
||||
Stop();
|
||||
|
||||
if (_outputStream != 0)
|
||||
{
|
||||
SDL_CloseAudioDevice(_outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -193,7 +193,7 @@ namespace Ryujinx.Input.SDL3
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static int ToSDL2Scancode(Key key)
|
||||
private static int ToSDL3Scancode(Key key)
|
||||
{
|
||||
if (key >= Key.Unknown && key <= Key.Menu)
|
||||
{
|
||||
@ -235,7 +235,7 @@ namespace Ryujinx.Input.SDL3
|
||||
|
||||
for (Key key = 0; key < Key.Count; key++)
|
||||
{
|
||||
int index = ToSDL2Scancode(key);
|
||||
int index = ToSDL3Scancode(key);
|
||||
if (index == -1)
|
||||
{
|
||||
SDL_Keymod modifierMask = GetKeyboardModifierMask(key);
|
||||
|
@ -8048,5 +8048,6 @@ public static unsafe partial class SDL
|
||||
[UnmanagedCallConv(CallConvs = [typeof(CallConvCdecl)])]
|
||||
public static partial int SDL_EnterAppMainCallbacks(int argc, IntPtr argv, SDL_AppInit_func appinit, SDL_AppIterate_func appiter, SDL_AppEvent_func appevent, SDL_AppQuit_func appquit);
|
||||
|
||||
public const uint SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK = 0xFFFFFFFFu;
|
||||
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ namespace Ryujinx.SDL3.Common
|
||||
|
||||
public static Action<Action> MainThreadDispatcher { get; set; }
|
||||
|
||||
private const SDL_InitFlags SdlInitFlags = SDL_InitFlags.SDL_INIT_GAMEPAD;
|
||||
private const SDL_InitFlags SdlInitFlags = SDL_InitFlags.SDL_INIT_GAMEPAD | SDL_InitFlags.SDL_INIT_AUDIO;
|
||||
|
||||
private bool _isRunning;
|
||||
private uint _refereceCount;
|
||||
@ -52,13 +52,13 @@ namespace Ryujinx.SDL3.Common
|
||||
return;
|
||||
}
|
||||
|
||||
// SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx");
|
||||
// SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
// SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
// SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
// SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
|
||||
// SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
|
||||
// SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
SDL_SetHint(SDL_HINT_APP_NAME, "Ryujinx");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0");
|
||||
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
|
||||
SDL_SetHint(SDL_HINT_VIDEO_ALLOW_SCREENSAVER, "1");
|
||||
//
|
||||
//
|
||||
// // NOTE: As of SDL2 2.24.0, joycons are combined by default but the motion source only come from one of them.
|
||||
@ -67,7 +67,7 @@ namespace Ryujinx.SDL3.Common
|
||||
|
||||
if (!SDL_Init(SdlInitFlags))
|
||||
{
|
||||
string errorMessage = $"SDL2 initialization failed with error \"{SDL_GetError()}\"";
|
||||
string errorMessage = $"SDL3 initialization failed with error \"{SDL_GetError()}\"";
|
||||
|
||||
Logger.Error?.Print(LogClass.Application, errorMessage);
|
||||
|
||||
|
@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Audio.Backends.Dummy;
|
||||
using Ryujinx.Audio.Backends.OpenAL;
|
||||
using Ryujinx.Audio.Backends.SDL2;
|
||||
using Ryujinx.Audio.Backends.SDL3;
|
||||
using Ryujinx.Audio.Backends.SoundIo;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Ava.Common;
|
||||
@ -957,6 +958,7 @@ namespace Ryujinx.Ava
|
||||
{
|
||||
var availableBackends = new List<AudioBackend>
|
||||
{
|
||||
AudioBackend.SDL3,
|
||||
AudioBackend.SDL2,
|
||||
AudioBackend.SoundIo,
|
||||
AudioBackend.OpenAl,
|
||||
@ -996,6 +998,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
deviceDriver = currentBackend switch
|
||||
{
|
||||
AudioBackend.SDL3 => InitializeAudioBackend<SDL3HardwareDeviceDriver>(AudioBackend.SDL3, nextBackend),
|
||||
AudioBackend.SDL2 => InitializeAudioBackend<SDL2HardwareDeviceDriver>(AudioBackend.SDL2, nextBackend),
|
||||
AudioBackend.SoundIo => InitializeAudioBackend<SoundIoHardwareDeviceDriver>(AudioBackend.SoundIo, nextBackend),
|
||||
AudioBackend.OpenAl => InitializeAudioBackend<OpenALHardwareDeviceDriver>(AudioBackend.OpenAl, nextBackend),
|
||||
|
@ -15,6 +15,7 @@ using Ryujinx.Input;
|
||||
using Ryujinx.Input.HLE;
|
||||
using Ryujinx.Input.SDL2;
|
||||
using Ryujinx.SDL2.Common;
|
||||
using Ryujinx.SDL3.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
@ -116,6 +117,7 @@ namespace Ryujinx.Headless
|
||||
HostUITheme = new HeadlessHostUiTheme();
|
||||
|
||||
SDL2Driver.Instance.Initialize();
|
||||
SDL3Driver.Instance.Initialize();
|
||||
}
|
||||
|
||||
public void Initialize(Switch device, List<InputConfig> inputConfigs, bool enableKeyboard, bool enableMouse)
|
||||
|
@ -71,6 +71,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL2\Ryujinx.Audio.Backends.SDL2.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Audio.Backends.SDL3\Ryujinx.Audio.Backends.SDL3.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Vulkan\Ryujinx.Graphics.Vulkan.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.OpenGL\Ryujinx.Graphics.OpenGL.csproj" />
|
||||
<ProjectReference Include="..\Ryujinx.Graphics.Metal\Ryujinx.Graphics.Metal.csproj" />
|
||||
|
@ -6,6 +6,7 @@ using Gommon;
|
||||
using LibHac.Tools.FsSystem;
|
||||
using Ryujinx.Audio.Backends.OpenAL;
|
||||
using Ryujinx.Audio.Backends.SDL2;
|
||||
using Ryujinx.Audio.Backends.SDL3;
|
||||
using Ryujinx.Audio.Backends.SoundIo;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
@ -212,6 +213,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
public bool IsOpenAlEnabled { get; set; }
|
||||
public bool IsSoundIoEnabled { get; set; }
|
||||
public bool IsSDL2Enabled { get; set; }
|
||||
public bool IsSDL3Enabled { get; set; }
|
||||
public bool IsCustomResolutionScaleActive => _resolutionScale == 4;
|
||||
public bool IsScalingFilterActive => _scalingFilter == (int)Ryujinx.Common.Configuration.ScalingFilter.Fsr;
|
||||
|
||||
@ -373,6 +375,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
IsOpenAlEnabled = OpenALHardwareDeviceDriver.IsSupported;
|
||||
IsSoundIoEnabled = SoundIoHardwareDeviceDriver.IsSupported;
|
||||
IsSDL2Enabled = SDL2HardwareDeviceDriver.IsSupported;
|
||||
IsSDL3Enabled = SDL3HardwareDeviceDriver.IsSupported;
|
||||
|
||||
await Dispatcher.UIThread.InvokeAsync(() =>
|
||||
{
|
||||
|
@ -46,6 +46,12 @@
|
||||
<ComboBoxItem IsEnabled="{Binding IsSDL2Enabled}">
|
||||
<TextBlock Text="{ext:Locale SettingsTabSystemAudioBackendSDL2}" />
|
||||
</ComboBoxItem>
|
||||
<ComboBoxItem IsEnabled="{Binding IsSDL3Enabled}">
|
||||
<TextBlock>
|
||||
<TextBlock Text="{ext:Locale SettingsTabSystemAudioBackendSDL2}" />
|
||||
<TextBlock Text="*" />
|
||||
</TextBlock>
|
||||
</ComboBoxItem>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
<StackPanel Margin="10,0,0,0" Orientation="Horizontal">
|
||||
|
@ -10,5 +10,6 @@ namespace Ryujinx.Ava.Utilities.Configuration
|
||||
OpenAl,
|
||||
SoundIo,
|
||||
SDL2,
|
||||
SDL3,
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user