diff --git a/README.md b/README.md index 5d9f2f2b..18010781 100644 --- a/README.md +++ b/README.md @@ -104,7 +104,7 @@ If you'd like to donate, please take a look at our [Patreon](https://www.patreon ## License This software is licensed under the terms of the MIT license. -The Ryujinx.Audio.Renderer project is licensed under the terms of the LGPLv3 license. +The Ryujinx.Audio project is licensed under the terms of the LGPLv3 license. This project makes use of code authored by the libvpx project, licensed under BSD and the ffmpeg project, licensed under LGPLv3. See [LICENSE.txt](LICENSE.txt) and [THIRDPARTY.md](Ryujinx/THIRDPARTY.md) for more details. diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs new file mode 100644 index 00000000..050b52a9 --- /dev/null +++ b/Ryujinx.Audio.Backends.OpenAL/OpenALAudioBuffer.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Audio.Backends.OpenAL +{ + class OpenALAudioBuffer + { + public int BufferId; + public ulong DriverIdentifier; + public ulong SampleCount; + } +} diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs new file mode 100644 index 00000000..43f238ef --- /dev/null +++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceDriver.cs @@ -0,0 +1,170 @@ +using OpenTK.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Threading; +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.OpenAL +{ + public class OpenALHardwareDeviceDriver : IHardwareDeviceDriver + { + private object _lock = new object(); + + private AudioContext _context; + private ManualResetEvent _updateRequiredEvent; + private List _sessions; + private bool _stillRunning; + private Thread _updaterThread; + + public OpenALHardwareDeviceDriver() + { + _context = new AudioContext(); + _updateRequiredEvent = new ManualResetEvent(false); + _sessions = new List(); + + _stillRunning = true; + _updaterThread = new Thread(Update) + { + Name = "HardwareDeviceDriver.OpenAL" + }; + + _updaterThread.Start(); + } + + public static bool IsSupported + { + get + { + try + { + return AudioContext.AvailableDevices.Count > 0; + } + catch + { + return false; + } + } + } + + 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 ArgumentException($"{direction}"); + } + else if (!SupportsChannelCount(channelCount)) + { + throw new ArgumentException($"{channelCount}"); + } + + lock (_lock) + { + OpenALHardwareDeviceSession session = new OpenALHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount); + + _sessions.Add(session); + + return session; + } + } + + internal void Unregister(OpenALHardwareDeviceSession session) + { + lock (_lock) + { + _sessions.Remove(session); + } + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + private void Update() + { + while (_stillRunning) + { + bool updateRequired = false; + + lock (_lock) + { + foreach (OpenALHardwareDeviceSession session in _sessions) + { + if (session.Update()) + { + updateRequired = true; + } + } + } + + if (updateRequired) + { + _updateRequiredEvent.Set(); + } + + // If it's not slept it will waste cycles. + Thread.Sleep(10); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_lock) + { + _stillRunning = false; + _updaterThread.Join(); + + // Loop against all sessions to dispose them (they will unregister themself) + while (_sessions.Count > 0) + { + OpenALHardwareDeviceSession session = _sessions[0]; + + session.Dispose(); + } + } + + _context.Dispose(); + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return true; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output; + } + } +} diff --git a/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs new file mode 100644 index 00000000..7bca4427 --- /dev/null +++ b/Ryujinx.Audio.Backends.OpenAL/OpenALHardwareDeviceSession.cs @@ -0,0 +1,213 @@ +using OpenTK.Audio.OpenAL; +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Collections.Generic; +using System.Diagnostics; + +namespace Ryujinx.Audio.Backends.OpenAL +{ + class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private OpenALHardwareDeviceDriver _driver; + private int _sourceId; + private ALFormat _targetFormat; + private bool _isActive; + private Queue _queuedBuffers; + private ulong _playedSampleCount; + + private object _lock = new object(); + + public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _driver = driver; + _queuedBuffers = new Queue(); + _sourceId = AL.GenSource(); + _targetFormat = GetALFormat(); + _isActive = false; + _playedSampleCount = 0; + } + + private ALFormat GetALFormat() + { + switch (RequestedSampleFormat) + { + case SampleFormat.PcmInt16: + switch (RequestedChannelCount) + { + case 1: + return ALFormat.Mono16; + case 2: + return ALFormat.Stereo16; + case 6: + return ALFormat.Multi51Chn16Ext; + default: + throw new NotImplementedException($"Unsupported channel config {RequestedChannelCount}"); + } + default: + throw new NotImplementedException($"Unsupported sample format {RequestedSampleFormat}"); + } + } + + public override void PrepareToClose() { } + + private void StartIfNotPlaying() + { + AL.GetSource(_sourceId, ALGetSourcei.SourceState, out int stateInt); + + ALSourceState State = (ALSourceState)stateInt; + + if (State != ALSourceState.Playing) + { + AL.SourcePlay(_sourceId); + } + } + + public override void QueueBuffer(AudioBuffer buffer) + { + lock (_lock) + { + OpenALAudioBuffer driverBuffer = new OpenALAudioBuffer + { + DriverIdentifier = buffer.DataPointer, + BufferId = AL.GenBuffer(), + SampleCount = GetSampleCount(buffer) + }; + + AL.BufferData(driverBuffer.BufferId, _targetFormat, buffer.Data, (int)buffer.DataSize, (int)RequestedSampleRate); + + _queuedBuffers.Enqueue(driverBuffer); + + AL.SourceQueueBuffer(_sourceId, driverBuffer.BufferId); + + if (_isActive) + { + StartIfNotPlaying(); + } + } + } + + public override void SetVolume(float volume) + { + lock (_lock) + { + AL.Source(_sourceId, ALSourcef.Gain, volume); + } + } + + public override float GetVolume() + { + AL.GetSource(_sourceId, ALSourcef.Gain, out float volume); + + return volume; + } + + public override void Start() + { + lock (_lock) + { + _isActive = true; + + StartIfNotPlaying(); + } + } + + public override void Stop() + { + lock (_lock) + { + SetVolume(0.0f); + + AL.SourceStop(_sourceId); + + _isActive = false; + } + } + + public override void UnregisterBuffer(AudioBuffer buffer) {} + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + lock (_lock) + { + if (!_queuedBuffers.TryPeek(out OpenALAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + } + + public override ulong GetPlayedSampleCount() + { + lock (_lock) + { + return _playedSampleCount; + } + } + + public bool Update() + { + lock (_lock) + { + if (_isActive) + { + AL.GetSource(_sourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); + + if (releasedCount > 0) + { + int[] bufferIds = new int[releasedCount]; + + AL.SourceUnqueueBuffers(_sourceId, releasedCount, bufferIds); + + int i = 0; + + while (_queuedBuffers.TryPeek(out OpenALAudioBuffer buffer) && i < bufferIds.Length) + { + if (buffer.BufferId == bufferIds[i]) + { + _playedSampleCount += buffer.SampleCount; + + _queuedBuffers.TryDequeue(out _); + + i++; + } + } + + Debug.Assert(i < bufferIds.Length, "Unknown buffer id"); + + AL.DeleteBuffers(bufferIds); + } + + return releasedCount > 0; + } + + return false; + } + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + lock (_lock) + { + PrepareToClose(); + Stop(); + + AL.DeleteSource(_sourceId); + + _driver.Unregister(this); + } + } + } + + public override void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + } +} diff --git a/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj b/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj new file mode 100644 index 00000000..187fe0fe --- /dev/null +++ b/Ryujinx.Audio.Backends.OpenAL/Ryujinx.Audio.Backends.OpenAL.csproj @@ -0,0 +1,15 @@ + + + + net5.0 + + + + + + + + + + + diff --git a/Ryujinx.Audio/Native/libsoundio/MarshalExtensions.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/MarshalExtensions.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/MarshalExtensions.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/MarshalExtensions.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIO.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIO.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIO.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIO.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOBackend.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOBackend.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOBackend.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOBackend.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOChannelArea.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOChannelArea.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOChannelArea.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOChannelArea.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOChannelAreas.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOChannelAreas.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOChannelAreas.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOChannelAreas.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOChannelId.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOChannelId.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOChannelId.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOChannelId.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOChannelLayout.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOChannelLayout.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOChannelLayout.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOChannelLayout.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIODevice.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIODevice.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIODevice.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIODevice.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIODeviceAim.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIODeviceAim.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIODeviceAim.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIODeviceAim.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOException.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOException.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOException.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOException.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOFormat.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOFormat.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOFormat.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOFormat.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOInStream.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOInStream.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOInStream.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOInStream.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOOutStream.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOOutStream.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOOutStream.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOOutStream.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIORingBuffer.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIORingBuffer.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIORingBuffer.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIORingBuffer.cs diff --git a/Ryujinx.Audio/Native/libsoundio/SoundIOSampleRateRange.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOSampleRateRange.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/SoundIOSampleRateRange.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/SoundIOSampleRateRange.cs diff --git a/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dll b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dll rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dll diff --git a/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dylib b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/libs/libsoundio.dylib rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.dylib diff --git a/Ryujinx.Audio/Native/libsoundio/libs/libsoundio.so b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/libs/libsoundio.so rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libs/libsoundio.so diff --git a/Ryujinx.Audio/Native/libsoundio/libsoundio-interop.cs b/Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libsoundio-interop.cs similarity index 100% rename from Ryujinx.Audio/Native/libsoundio/libsoundio-interop.cs rename to Ryujinx.Audio.Backends.SoundIo/Native/libsoundio/libsoundio-interop.cs diff --git a/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj b/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj new file mode 100644 index 00000000..a9a2fe75 --- /dev/null +++ b/Ryujinx.Audio.Backends.SoundIo/Ryujinx.Audio.Backends.SoundIo.csproj @@ -0,0 +1,27 @@ + + + + net5.0 + true + + + + + + + + + PreserveNewest + libsoundio.dll + + + PreserveNewest + libsoundio.dylib + + + PreserveNewest + libsoundio.so + + + + diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs new file mode 100644 index 00000000..b43143d6 --- /dev/null +++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs @@ -0,0 +1,9 @@ +namespace Ryujinx.Audio.Backends.SoundIo +{ + class SoundIoAudioBuffer + { + public ulong DriverIdentifier; + public ulong SampleCount; + public ulong SamplePlayed; + } +} diff --git a/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs new file mode 100644 index 00000000..00977fcb --- /dev/null +++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceDriver.cs @@ -0,0 +1,251 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using SoundIOSharp; +using System; +using System.Collections.Generic; +using System.Threading; + +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.SoundIo +{ + public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver + { + private object _lock = new object(); + + private SoundIO _audioContext; + private SoundIODevice _audioDevice; + private ManualResetEvent _updateRequiredEvent; + private List _sessions; + + public SoundIoHardwareDeviceDriver() + { + _audioContext = new SoundIO(); + _updateRequiredEvent = new ManualResetEvent(false); + _sessions = new List(); + + _audioContext.Connect(); + _audioContext.FlushEvents(); + + _audioDevice = FindNonRawDefaultAudioDevice(_audioContext, true); + } + + public static bool IsSupported => IsSupportedInternal(); + + private static bool IsSupportedInternal() + { + SoundIO context = null; + SoundIODevice device = null; + SoundIOOutStream stream = null; + + bool backendDisconnected = false; + + try + { + context = new SoundIO(); + + context.OnBackendDisconnect = (i) => + { + backendDisconnected = true; + }; + + context.Connect(); + context.FlushEvents(); + + if (backendDisconnected) + { + return false; + } + + if (context.OutputDeviceCount == 0) + { + return false; + } + + device = FindNonRawDefaultAudioDevice(context); + + if (device == null || backendDisconnected) + { + return false; + } + + stream = device.CreateOutStream(); + + if (stream == null || backendDisconnected) + { + return false; + } + + return true; + } + catch + { + return false; + } + finally + { + if (stream != null) + { + stream.Dispose(); + } + + if (context != null) + { + context.Dispose(); + } + } + } + + private static SoundIODevice FindNonRawDefaultAudioDevice(SoundIO audioContext, bool fallback = false) + { + SoundIODevice defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex); + + if (!defaultAudioDevice.IsRaw) + { + return defaultAudioDevice; + } + + for (int i = 0; i < audioContext.BackendCount; i++) + { + SoundIODevice audioDevice = audioContext.GetOutputDevice(i); + + if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw) + { + return audioDevice; + } + } + + return fallback ? defaultAudioDevice : null; + } + + 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 SoundIO backend!"); + } + + lock (_lock) + { + SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount); + + _sessions.Add(session); + + return session; + } + } + + internal void Unregister(SoundIoHardwareDeviceSession session) + { + lock (_lock) + { + _sessions.Remove(session); + } + } + + public static SoundIOFormat GetSoundIoFormat(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => SoundIOFormat.S8, + SampleFormat.PcmInt16 => SoundIOFormat.S16LE, + SampleFormat.PcmInt24 => SoundIOFormat.S24LE, + SampleFormat.PcmInt32 => SoundIOFormat.S32LE, + SampleFormat.PcmFloat => SoundIOFormat.Float32LE, + _ => throw new ArgumentException ($"Unsupported sample format {format}"), + }; + } + + internal SoundIOOutStream OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + SoundIOFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat); + + 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}"); + } + + SoundIOOutStream result = _audioDevice.CreateOutStream(); + + result.Name = "Ryujinx"; + result.Layout = SoundIOChannelLayout.GetDefault((int)requestedChannelCount); + result.Format = driverSampleFormat; + result.SampleRate = (int)requestedSampleRate; + + return result; + } + + internal void FlushContextEvents() + { + _audioContext.FlushEvents(); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + while (_sessions.Count > 0) + { + SoundIoHardwareDeviceSession session = _sessions[_sessions.Count - 1]; + + session.Dispose(); + } + + _audioContext.Disconnect(); + _audioContext.Dispose(); + } + } + + 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; + } + } +} diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs similarity index 53% rename from Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs rename to Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs index 52c4ebc9..be01ecdc 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs +++ b/Ryujinx.Audio.Backends.SoundIo/SoundIoHardwareDeviceSession.cs @@ -1,155 +1,118 @@ -using SoundIOSharp; +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using SoundIOSharp; using System; -using System.Collections.Concurrent; -using System.Linq; +using System.Collections.Generic; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; +using System.Threading; -namespace Ryujinx.Audio.SoundIo +namespace Ryujinx.Audio.Backends.SoundIo { - internal class SoundIoAudioTrack : IDisposable + class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase { - /// - /// The audio track ring buffer - /// - private SoundIoRingBuffer m_Buffer; + private object _lock = new object(); - /// - /// A list of buffers currently pending writeback to the audio backend - /// - private ConcurrentQueue m_ReservedBuffers; + private SoundIoHardwareDeviceDriver _driver; + private Queue _queuedBuffers; + private SoundIOOutStream _outputStream; + private DynamicRingBuffer _ringBuffer; + private ulong _playedSampleCount; + private ManualResetEvent _updateRequiredEvent; - /// - /// Occurs when a buffer has been released by the audio backend - /// - private event ReleaseCallback BufferReleased; - - /// - /// The track ID of this - /// - public int TrackID { get; private set; } - - /// - /// The current playback state - /// - public PlaybackState State { get; private set; } - - /// - /// The audio context this track belongs to - /// - public SoundIO AudioContext { get; private set; } - - /// - /// The this track belongs to - /// - public SoundIODevice AudioDevice { get; private set; } - - /// - /// The audio output stream of this track - /// - public SoundIOOutStream AudioStream { get; private set; } - - /// - /// Released buffers the track is no longer holding - /// - public ConcurrentQueue ReleasedBuffers { get; private set; } - - /// - /// Buffer count of the track - /// - public uint BufferCount => (uint)m_ReservedBuffers.Count; - - /// - /// Played sample count of the track - /// - public ulong PlayedSampleCount { get; private set; } - - private int _hardwareChannels; - private int _virtualChannels; - - /// - /// Constructs a new instance of a - /// - /// The track ID - /// The SoundIO audio context - /// The SoundIO audio device - public SoundIoAudioTrack(int trackId, SoundIO audioContext, SoundIODevice audioDevice) + public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { - TrackID = trackId; - AudioContext = audioContext; - AudioDevice = audioDevice; - State = PlaybackState.Stopped; - ReleasedBuffers = new ConcurrentQueue(); + _driver = driver; + _updateRequiredEvent = _driver.GetUpdateRequiredEvent(); + _queuedBuffers = new Queue(); + _ringBuffer = new DynamicRingBuffer(); - m_Buffer = new SoundIoRingBuffer(); - m_ReservedBuffers = new ConcurrentQueue(); + SetupOutputStream(); } - /// - /// Opens the audio track with the specified parameters - /// - /// The requested sample rate of the track - /// The requested hardware channels - /// The requested virtual channels - /// A that represents the delegate to invoke when a buffer has been released by the audio track - /// The requested sample format of the track - public void Open( - int sampleRate, - int hardwareChannels, - int virtualChannels, - ReleaseCallback callback, - SoundIOFormat format = SoundIOFormat.S16LE) + private void SetupOutputStream() { - // Close any existing audio streams - if (AudioStream != null) - { - Close(); - } + _outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount); + _outputStream.WriteCallback += Update; - if (!AudioDevice.SupportsSampleRate(sampleRate)) - { - throw new InvalidOperationException($"This sound device does not support a sample rate of {sampleRate}Hz"); - } + // TODO: Setup other callbacks (errors, ect). - if (!AudioDevice.SupportsFormat(format)) - { - throw new InvalidOperationException($"This sound device does not support SoundIOFormat.{Enum.GetName(typeof(SoundIOFormat), format)}"); - } - - if (!AudioDevice.SupportsChannelCount(hardwareChannels)) - { - throw new InvalidOperationException($"This sound device does not support channel count {hardwareChannels}"); - } - - _hardwareChannels = hardwareChannels; - _virtualChannels = virtualChannels; - - AudioStream = AudioDevice.CreateOutStream(); - - AudioStream.Name = $"SwitchAudioTrack_{TrackID}"; - AudioStream.Layout = SoundIOChannelLayout.GetDefault(hardwareChannels); - AudioStream.Format = format; - AudioStream.SampleRate = sampleRate; - - AudioStream.WriteCallback = WriteCallback; - - BufferReleased += callback; - - AudioStream.Open(); + _outputStream.Open(); } - /// - /// This callback occurs when the sound device is ready to buffer more frames - /// - /// The minimum amount of frames expected by the audio backend - /// The maximum amount of frames that can be written to the audio backend - private unsafe void WriteCallback(int minFrameCount, int maxFrameCount) + public override ulong GetPlayedSampleCount() { - int bytesPerFrame = AudioStream.BytesPerFrame; - uint bytesPerSample = (uint)AudioStream.BytesPerSample; + lock (_lock) + { + return _playedSampleCount; + } + } - int bufferedFrames = m_Buffer.Length / bytesPerFrame; - long bufferedSamples = m_Buffer.Length / bytesPerSample; + public override float GetVolume() + { + return _outputStream.Volume; + } + + public override void PrepareToClose() { } + + public override void QueueBuffer(AudioBuffer buffer) + { + lock (_lock) + { + SoundIoAudioBuffer driverBuffer = new SoundIoAudioBuffer + { + DriverIdentifier = buffer.DataPointer, + SampleCount = GetSampleCount(buffer), + SamplePlayed = 0, + }; + + _ringBuffer.Write(buffer.Data, 0, buffer.Data.Length); + + _queuedBuffers.Enqueue(driverBuffer); + } + } + + public override void SetVolume(float volume) + { + _outputStream.SetVolume(volume); + } + + public override void Start() + { + _outputStream.Start(); + _outputStream.Pause(false); + + _driver.FlushContextEvents(); + } + + public override void Stop() + { + _outputStream.Pause(true); + + _driver.FlushContextEvents(); + } + + public override void UnregisterBuffer(AudioBuffer buffer) {} + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + lock (_lock) + { + if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) + { + return true; + } + + return driverBuffer.DriverIdentifier != buffer.DataPointer; + } + } + + private unsafe void Update(int minFrameCount, int maxFrameCount) + { + int bytesPerFrame = _outputStream.BytesPerFrame; + uint bytesPerSample = (uint)_outputStream.BytesPerSample; + + int bufferedFrames = _ringBuffer.Length / bytesPerFrame; int frameCount = Math.Min(bufferedFrames, maxFrameCount); @@ -158,16 +121,18 @@ namespace Ryujinx.Audio.SoundIo return; } - SoundIOChannelAreas areas = AudioStream.BeginWrite(ref frameCount); + SoundIOChannelAreas areas = _outputStream.BeginWrite(ref frameCount); + int channelCount = areas.ChannelCount; byte[] samples = new byte[frameCount * bytesPerFrame]; - m_Buffer.Read(samples, 0, samples.Length); + _ringBuffer.Read(samples, 0, samples.Length); // This is a huge ugly block of code, but we save // a significant amount of time over the generic // loop that handles other channel counts. + // TODO: Is this still right in 2021? // Mono if (channelCount == 1) @@ -438,209 +403,58 @@ namespace Ryujinx.Audio.SoundIo } } - AudioStream.EndWrite(); + _outputStream.EndWrite(); - PlayedSampleCount += (ulong)samples.Length; + ulong sampleCount = (ulong)(samples.Length / bytesPerSample / channelCount); - UpdateReleasedBuffers(samples.Length); - } + ulong availaibleSampleCount = sampleCount; - /// - /// Releases any buffers that have been fully written to the output device - /// - /// The amount of bytes written in the last device write - private void UpdateReleasedBuffers(int bytesRead) - { - bool bufferReleased = false; + bool needUpdate = false; - while (bytesRead > 0) + while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer)) { - if (m_ReservedBuffers.TryPeek(out SoundIoBuffer buffer)) + ulong sampleStillNeeded = driverBuffer.SampleCount - driverBuffer.SamplePlayed; + ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount); + + driverBuffer.SamplePlayed += playedAudioBufferSampleCount; + availaibleSampleCount -= playedAudioBufferSampleCount; + + if (driverBuffer.SamplePlayed == driverBuffer.SampleCount) { - if (buffer.Length > bytesRead) - { - buffer.Length -= bytesRead; - bytesRead = 0; - } - else - { - bufferReleased = true; - bytesRead -= buffer.Length; + _queuedBuffers.TryDequeue(out _); - m_ReservedBuffers.TryDequeue(out buffer); - ReleasedBuffers.Enqueue(buffer.Tag); - } - } - } - - if (bufferReleased) - { - OnBufferReleased(); - } - } - - /// - /// Starts audio playback - /// - public void Start() - { - if (AudioStream == null) - { - return; - } - - AudioStream.Start(); - AudioStream.Pause(false); - AudioContext.FlushEvents(); - State = PlaybackState.Playing; - } - - /// - /// Stops audio playback - /// - public void Stop() - { - if (AudioStream == null) - { - return; - } - - AudioStream.Pause(true); - AudioContext.FlushEvents(); - State = PlaybackState.Stopped; - } - - /// - /// Appends an audio buffer to the tracks internal ring buffer - /// - /// The audio sample type - /// The unqiue tag of the buffer being appended - /// The buffer to append - public void AppendBuffer(long bufferTag, T[] buffer) where T: struct - { - if (AudioStream == null) - { - return; - } - - int sampleSize = Unsafe.SizeOf(); - int targetSize = sampleSize * buffer.Length; - - // Do we need to downmix? - if (_hardwareChannels != _virtualChannels) - { - if (sampleSize != sizeof(short)) - { - throw new NotImplementedException("Downmixing formats other than PCM16 is not supported!"); + needUpdate = true; } - short[] downmixedBuffer; - - ReadOnlySpan bufferPCM16 = MemoryMarshal.Cast(buffer); - - if (_virtualChannels == 6) - { - downmixedBuffer = Downmixing.DownMixSurroundToStereo(bufferPCM16); - - if (_hardwareChannels == 1) - { - downmixedBuffer = Downmixing.DownMixStereoToMono(downmixedBuffer); - } - } - else if (_virtualChannels == 2) - { - downmixedBuffer = Downmixing.DownMixStereoToMono(bufferPCM16); - } - else - { - throw new NotImplementedException($"Downmixing from {_virtualChannels} to {_hardwareChannels} not implemented!"); - } - - targetSize = sampleSize * downmixedBuffer.Length; - - // Copy the memory to our ring buffer - m_Buffer.Write(downmixedBuffer, 0, targetSize); - - // Keep track of "buffered" buffers - m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, targetSize)); + _playedSampleCount += playedAudioBufferSampleCount; } - else + + // Notify the output if needed. + if (needUpdate) { - // Copy the memory to our ring buffer - m_Buffer.Write(buffer, 0, targetSize); - - // Keep track of "buffered" buffers - m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, targetSize)); + _updateRequiredEvent.Set(); } } - /// - /// Returns a value indicating whether the specified buffer is currently reserved by the track - /// - /// The buffer tag to check - public bool ContainsBuffer(long bufferTag) + protected virtual void Dispose(bool disposing) { - return m_ReservedBuffers.Any(x => x.Tag == bufferTag); - } - - /// - /// Flush all track buffers - /// - public bool FlushBuffers() - { - m_Buffer.Clear(); - - if (m_ReservedBuffers.Count > 0) + if (disposing) { - foreach (var buffer in m_ReservedBuffers) + lock (_lock) { - ReleasedBuffers.Enqueue(buffer.Tag); + PrepareToClose(); + Stop(); + + _outputStream.Dispose(); + + _driver.Unregister(this); } - - OnBufferReleased(); - - return true; } - - return false; } - /// - /// Closes the - /// - public void Close() + public override void Dispose() { - if (AudioStream != null) - { - AudioStream.Pause(true); - AudioStream.Dispose(); - } - - m_Buffer.Clear(); - OnBufferReleased(); - ReleasedBuffers.Clear(); - - State = PlaybackState.Stopped; - AudioStream = null; - BufferReleased = null; - } - - private void OnBufferReleased() - { - BufferReleased?.Invoke(); - } - - /// - /// Releases the unmanaged resources used by the - /// - public void Dispose() - { - Close(); - } - - ~SoundIoAudioTrack() - { - Dispose(); + Dispose(true); } } } diff --git a/Ryujinx.Audio.Backends/Ryujinx.Audio.Backends.csproj b/Ryujinx.Audio.Backends/Ryujinx.Audio.Backends.csproj new file mode 100644 index 00000000..431187ed --- /dev/null +++ b/Ryujinx.Audio.Backends/Ryujinx.Audio.Backends.csproj @@ -0,0 +1,32 @@ + + + + net5.0 + true + + + + + + + + + + + + + + PreserveNewest + libsoundio.dll + + + PreserveNewest + libsoundio.dylib + + + PreserveNewest + libsoundio.so + + + + diff --git a/Ryujinx.Audio.Renderer/Ryujinx.Audio.Renderer.csproj b/Ryujinx.Audio.Renderer/Ryujinx.Audio.Renderer.csproj deleted file mode 100644 index ccdeae3e..00000000 --- a/Ryujinx.Audio.Renderer/Ryujinx.Audio.Renderer.csproj +++ /dev/null @@ -1,14 +0,0 @@ - - - - net5.0 - true - - - - - - - - - diff --git a/Ryujinx.Audio/AudioManager.cs b/Ryujinx.Audio/AudioManager.cs new file mode 100644 index 00000000..ab25150a --- /dev/null +++ b/Ryujinx.Audio/AudioManager.cs @@ -0,0 +1,137 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; +using System.Threading; + +namespace Ryujinx.Audio +{ + /// + /// Manage audio input and output system. + /// + public class AudioManager : IDisposable + { + /// + /// Lock used to control the waiters registration. + /// + private object _lock = new object(); + + /// + /// Events signaled when the driver played audio buffers. + /// + private ManualResetEvent[] _updateRequiredEvents; + + /// + /// Action to execute when the driver played audio buffers. + /// + private Action[] _actions; + + /// + /// The worker thread in charge of handling sessions update. + /// + private Thread _workerThread; + + /// + /// Create a new . + /// + public AudioManager() + { + _updateRequiredEvents = new ManualResetEvent[2]; + _actions = new Action[2]; + + // Termination event. + _updateRequiredEvents[1] = new ManualResetEvent(false); + + _workerThread = new Thread(Update) + { + Name = "AudioManager.Worker" + }; + } + + /// + /// Start the . + /// + public void Start() + { + if (_workerThread.IsAlive) + { + throw new InvalidOperationException(); + } + + _workerThread.Start(); + } + + /// + /// Initialize update handlers. + /// + /// The driver event that will get signaled by the device driver when an audio buffer finished playing/being captured + /// The callback to call when an audio buffer finished playing + /// The callback to call when an audio buffer was captured + public void Initialize(ManualResetEvent updatedRequiredEvent, Action outputCallback, Action inputCallback) + { + lock (_lock) + { + _updateRequiredEvents[0] = updatedRequiredEvent; + _actions[0] = outputCallback; + _actions[1] = inputCallback; + } + } + + /// + /// Entrypoint of the in charge of updating the . + /// + private void Update() + { + while (true) + { + int index = WaitHandle.WaitAny(_updateRequiredEvents); + + // Last index is here to indicate thread termination. + if (index + 1 == _updateRequiredEvents.Length) + { + break; + } + + lock (_lock) + { + foreach (Action action in _actions) + { + action?.Invoke(); + } + + _updateRequiredEvents[0].Reset(); + } + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _updateRequiredEvents[1].Set(); + _workerThread.Join(); + + _updateRequiredEvents[1].Dispose(); + } + } + } +} diff --git a/Ryujinx.Audio/Backends/Common/BackendHelper.cs b/Ryujinx.Audio/Backends/Common/BackendHelper.cs new file mode 100644 index 00000000..df81d942 --- /dev/null +++ b/Ryujinx.Audio/Backends/Common/BackendHelper.cs @@ -0,0 +1,43 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using System; + +namespace Ryujinx.Audio.Backends.Common +{ + public static class BackendHelper + { + public static int GetSampleSize(SampleFormat format) + { + return format switch + { + SampleFormat.PcmInt8 => sizeof(byte), + SampleFormat.PcmInt16 => sizeof(ushort), + SampleFormat.PcmInt24 => 3, + SampleFormat.PcmInt32 => sizeof(int), + SampleFormat.PcmFloat => sizeof(float), + _ => throw new ArgumentException($"{format}"), + }; + } + + public static int GetSampleCount(SampleFormat format, int channelCount, int bufferSize) + { + return bufferSize / GetSampleSize(format) / channelCount; + } + } +} diff --git a/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs new file mode 100644 index 00000000..b4f5f812 --- /dev/null +++ b/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs @@ -0,0 +1,183 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common; +using System; + +namespace Ryujinx.Audio.Backends.Common +{ + /// + /// A ring buffer that grow if data written to it is too big to fit. + /// + public class DynamicRingBuffer + { + private const int RingBufferAlignment = 2048; + + private object _lock = new object(); + + private byte[] _buffer; + private int _size; + private int _headOffset; + private int _tailOffset; + + public int Length => _size; + + public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) + { + _buffer = new byte[initialCapacity]; + } + + public void Clear() + { + _size = 0; + _headOffset = 0; + _tailOffset = 0; + } + + public void Clear(int size) + { + lock (_lock) + { + if (size > _size) + { + size = _size; + } + + if (size == 0) + { + return; + } + + _headOffset = (_headOffset + size) % _buffer.Length; + _size -= size; + + if (_size == 0) + { + _headOffset = 0; + _tailOffset = 0; + } + } + } + + private void SetCapacityLocked(int capacity) + { + byte[] buffer = new byte[capacity]; + + if (_size > 0) + { + if (_headOffset < _tailOffset) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size); + } + else + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset); + Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset); + } + } + + _buffer = buffer; + _headOffset = 0; + _tailOffset = _size; + } + + + public void Write(T[] buffer, int index, int count) + { + if (count == 0) + { + return; + } + + lock (_lock) + { + if ((_size + count) > _buffer.Length) + { + SetCapacityLocked(BitUtils.AlignUp(_size + count, RingBufferAlignment)); + } + + if (_headOffset < _tailOffset) + { + int tailLength = _buffer.Length - _tailOffset; + + if (tailLength >= count) + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + } + else + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength); + Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength); + } + } + else + { + Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + } + + _size += count; + _tailOffset = (_tailOffset + count) % _buffer.Length; + } + } + + public int Read(T[] buffer, int index, int count) + { + lock (_lock) + { + if (count > _size) + { + count = _size; + } + + if (count == 0) + { + return 0; + } + + if (_headOffset < _tailOffset) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + } + else + { + int tailLength = _buffer.Length - _headOffset; + + if (tailLength >= count) + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + } + else + { + Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength); + Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength); + } + } + + _size -= count; + _headOffset = (_headOffset + count) % _buffer.Length; + + if (_size == 0) + { + _headOffset = 0; + _tailOffset = 0; + } + + return count; + } + } + } +} diff --git a/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs b/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs new file mode 100644 index 00000000..1e000e4c --- /dev/null +++ b/Ryujinx.Audio/Backends/Common/HardwareDeviceSessionOutputBase.cs @@ -0,0 +1,89 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; + +namespace Ryujinx.Audio.Backends.Common +{ + public abstract class HardwareDeviceSessionOutputBase : IHardwareDeviceSession + { + public IVirtualMemoryManager MemoryManager { get; } + public SampleFormat RequestedSampleFormat { get; } + public uint RequestedSampleRate { get; } + public uint RequestedChannelCount { get; } + + public HardwareDeviceSessionOutputBase(IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + MemoryManager = memoryManager; + RequestedSampleFormat = requestedSampleFormat; + RequestedSampleRate = requestedSampleRate; + RequestedChannelCount = requestedChannelCount; + } + + private byte[] GetBufferSamples(AudioBuffer buffer) + { + if (buffer.DataPointer == 0) + { + return null; + } + + byte[] data = new byte[buffer.DataSize]; + + MemoryManager.Read(buffer.DataPointer, data); + + return data; + } + + protected ulong GetSampleCount(AudioBuffer buffer) + { + return (ulong)BackendHelper.GetSampleCount(RequestedSampleFormat, (int)RequestedChannelCount, (int)buffer.DataSize); + } + + public abstract void Dispose(); + public abstract void PrepareToClose(); + public abstract void QueueBuffer(AudioBuffer buffer); + public abstract void SetVolume(float volume); + public abstract float GetVolume(); + public abstract void Start(); + public abstract void Stop(); + public abstract ulong GetPlayedSampleCount(); + public abstract bool WasBufferFullyConsumed(AudioBuffer buffer); + public virtual bool RegisterBuffer(AudioBuffer buffer) + { + return RegisterBuffer(buffer, GetBufferSamples(buffer)); + } + + public virtual bool RegisterBuffer(AudioBuffer buffer, byte[] samples) + { + if (samples == null) + { + return false; + } + + if (buffer.Data == null) + { + buffer.Data = samples; + } + + return true; + } + + public virtual void UnregisterBuffer(AudioBuffer buffer) { } + } +} diff --git a/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs b/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs new file mode 100644 index 00000000..c0305f8a --- /dev/null +++ b/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceDriver.cs @@ -0,0 +1,146 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Threading; + +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + public class CompatLayerHardwareDeviceDriver : IHardwareDeviceDriver + { + private IHardwareDeviceDriver _realDriver; + + public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice) + { + _realDriver = realDevice; + } + + public void Dispose() + { + _realDriver.Dispose(); + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _realDriver.GetUpdateRequiredEvent(); + } + + private uint SelectHardwareChannelCount(uint targetChannelCount) + { + if (_realDriver.SupportsChannelCount(targetChannelCount)) + { + return targetChannelCount; + } + + return targetChannelCount switch + { + 6 => SelectHardwareChannelCount(2), + 2 => SelectHardwareChannelCount(1), + 1 => throw new ArgumentException("No valid channel configuration found!"), + _ => throw new ArgumentException($"Invalid targetChannelCount {targetChannelCount}") + }; + } + + 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 (!_realDriver.SupportsDirection(direction)) + { + if (direction == Direction.Input) + { + Logger.Warning?.Print(LogClass.Audio, "The selected audio backend doesn't support audio input, fallback to dummy..."); + + return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount); + } + + throw new NotImplementedException(); + } + + uint hardwareChannelCount = SelectHardwareChannelCount(channelCount); + + IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, sampleFormat, sampleRate, hardwareChannelCount); + + if (hardwareChannelCount == channelCount) + { + return realSession; + } + + if (direction == Direction.Input) + { + Logger.Warning?.Print(LogClass.Audio, $"The selected audio backend doesn't support the requested audio input configuration, fallback to dummy..."); + + // TODO: We currently don't support audio input upsampling/downsampling, implement this. + realSession.Dispose(); + + return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount); + } + + // It must be a HardwareDeviceSessionOutputBase. + if (realSession is not HardwareDeviceSessionOutputBase realSessionOutputBase) + { + throw new InvalidOperationException($"Real driver session class type isn't based on {typeof(HardwareDeviceSessionOutputBase).Name}."); + } + + // If we need to do post processing before sending to the hardware device, wrap around it. + return new CompatLayerHardwareDeviceSession(realSessionOutputBase, channelCount); + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + // TODO: More formats. + return sampleFormat == SampleFormat.PcmInt16; + } + + public bool SupportsSampleRate(uint sampleRate) + { + // TODO: More sample rates. + return sampleRate == Constants.TargetSampleRate; + } + + public IHardwareDeviceDriver GetRealDeviceDriver() + { + return _realDriver; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Input || direction == Direction.Output; + } + } +} diff --git a/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs b/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs new file mode 100644 index 00000000..493d682f --- /dev/null +++ b/Ryujinx.Audio/Backends/CompatLayer/CompatLayerHardwareDeviceSession.cs @@ -0,0 +1,140 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Backends.CompatLayer +{ + class CompatLayerHardwareDeviceSession : HardwareDeviceSessionOutputBase + { + private HardwareDeviceSessionOutputBase _realSession; + private uint _userChannelCount; + + public CompatLayerHardwareDeviceSession(HardwareDeviceSessionOutputBase realSession, uint userChannelCount) : base(realSession.MemoryManager, realSession.RequestedSampleFormat, realSession.RequestedSampleRate, userChannelCount) + { + _realSession = realSession; + _userChannelCount = userChannelCount; + } + + public override void Dispose() + { + _realSession.Dispose(); + } + + public override ulong GetPlayedSampleCount() + { + return _realSession.GetPlayedSampleCount(); + } + + public override float GetVolume() + { + return _realSession.GetVolume(); + } + + public override void PrepareToClose() + { + _realSession.PrepareToClose(); + } + + public override void QueueBuffer(AudioBuffer buffer) + { + _realSession.QueueBuffer(buffer); + } + + public override bool RegisterBuffer(AudioBuffer buffer, byte[] samples) + { + if (RequestedSampleFormat != SampleFormat.PcmInt16) + { + throw new NotImplementedException("Downmixing formats other than PCM16 is not supported."); + } + + if (samples == null) + { + return false; + } + + short[] downmixedBufferPCM16; + + ReadOnlySpan samplesPCM16 = MemoryMarshal.Cast(samples); + + if (_userChannelCount == 6) + { + downmixedBufferPCM16 = Downmixing.DownMixSurroundToStereo(samplesPCM16); + + if (_realSession.RequestedChannelCount == 1) + { + downmixedBufferPCM16 = Downmixing.DownMixStereoToMono(downmixedBufferPCM16); + } + } + else if (_userChannelCount == 2 && _realSession.RequestedChannelCount == 1) + { + downmixedBufferPCM16 = Downmixing.DownMixStereoToMono(samplesPCM16); + } + else + { + throw new NotImplementedException($"Downmixing from {_userChannelCount} to {_realSession.RequestedChannelCount} not implemented."); + } + + byte[] downmixedBuffer = MemoryMarshal.Cast(downmixedBufferPCM16).ToArray(); + + AudioBuffer fakeBuffer = new AudioBuffer + { + BufferTag = buffer.BufferTag, + DataPointer = buffer.DataPointer, + DataSize = (ulong)downmixedBuffer.Length + }; + + bool result = _realSession.RegisterBuffer(fakeBuffer, downmixedBuffer); + + if (result) + { + buffer.Data = fakeBuffer.Data; + buffer.DataSize = fakeBuffer.DataSize; + } + + return result; + } + + public override void SetVolume(float volume) + { + _realSession.SetVolume(volume); + } + + public override void Start() + { + _realSession.Start(); + } + + public override void Stop() + { + _realSession.Stop(); + } + + public override void UnregisterBuffer(AudioBuffer buffer) + { + _realSession.UnregisterBuffer(buffer); + } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return _realSession.WasBufferFullyConsumed(buffer); + } + } +} diff --git a/Ryujinx.Audio/Downmixing.cs b/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs similarity index 86% rename from Ryujinx.Audio/Downmixing.cs rename to Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs index 03847737..ff4ba7a1 100644 --- a/Ryujinx.Audio/Downmixing.cs +++ b/Ryujinx.Audio/Backends/CompatLayer/Downmixing.cs @@ -1,8 +1,25 @@ -using System; +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -namespace Ryujinx.Audio +namespace Ryujinx.Audio.Backends.CompatLayer { public static class Downmixing { diff --git a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs new file mode 100644 index 00000000..f24b359c --- /dev/null +++ b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceDriver.cs @@ -0,0 +1,96 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Threading; + +using static Ryujinx.Audio.Integration.IHardwareDeviceDriver; + +namespace Ryujinx.Audio.Backends.Dummy +{ + public class DummyHardwareDeviceDriver : IHardwareDeviceDriver + { + private ManualResetEvent _updateRequiredEvent; + + public DummyHardwareDeviceDriver() + { + _updateRequiredEvent = new ManualResetEvent(false); + } + + public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount) + { + if (sampleRate == 0) + { + sampleRate = Constants.TargetSampleRate; + } + + if (channelCount == 0) + { + channelCount = 2; + } + + if (direction == Direction.Output) + { + return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount); + } + else + { + return new DummyHardwareDeviceSessionInput(this, memoryManager, sampleFormat, sampleRate, channelCount); + } + } + + public ManualResetEvent GetUpdateRequiredEvent() + { + return _updateRequiredEvent; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // NOTE: The _updateRequiredEvent will be disposed somewhere else. + } + } + + public bool SupportsSampleRate(uint sampleRate) + { + return true; + } + + public bool SupportsSampleFormat(SampleFormat sampleFormat) + { + return true; + } + + public bool SupportsDirection(Direction direction) + { + return direction == Direction.Output || direction == Direction.Input; + } + + public bool SupportsChannelCount(uint channelCount) + { + return channelCount == 1 || channelCount == 2 || channelCount == 6; + } + } +} diff --git a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs new file mode 100644 index 00000000..451a8564 --- /dev/null +++ b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionInput.cs @@ -0,0 +1,84 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System; + +namespace Ryujinx.Audio.Backends.Dummy +{ + class DummyHardwareDeviceSessionInput : IHardwareDeviceSession + { + private float _volume; + private IHardwareDeviceDriver _manager; + private IVirtualMemoryManager _memoryManager; + + public DummyHardwareDeviceSessionInput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) + { + _volume = 1.0f; + _manager = manager; + _memoryManager = memoryManager; + } + + public void Dispose() + { + // Nothing to do. + } + + public ulong GetPlayedSampleCount() + { + // Not implemented for input. + throw new NotSupportedException(); + } + + public float GetVolume() + { + return _volume; + } + + public void PrepareToClose() { } + + public void QueueBuffer(AudioBuffer buffer) + { + _memoryManager.Fill(buffer.DataPointer, buffer.DataSize, 0); + + _manager.GetUpdateRequiredEvent().Set(); + } + + public bool RegisterBuffer(AudioBuffer buffer) + { + return buffer.DataPointer != 0; + } + + public void SetVolume(float volume) + { + _volume = volume; + } + + public void Start() { } + + public void Stop() { } + + public void UnregisterBuffer(AudioBuffer buffer) { } + + public bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return true; + } + } +} diff --git a/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs new file mode 100644 index 00000000..7cc19997 --- /dev/null +++ b/Ryujinx.Audio/Backends/Dummy/DummyHardwareDeviceSessionOutput.cs @@ -0,0 +1,79 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Backends.Common; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Memory; +using System.Threading; + +namespace Ryujinx.Audio.Backends.Dummy +{ + internal class DummyHardwareDeviceSessionOutput : HardwareDeviceSessionOutputBase + { + private float _volume; + private IHardwareDeviceDriver _manager; + + private ulong _playedSampleCount; + + public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) + { + _volume = 1.0f; + _manager = manager; + } + + public override void Dispose() + { + // Nothing to do. + } + + 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) + { + Interlocked.Add(ref _playedSampleCount, GetSampleCount(buffer)); + + _manager.GetUpdateRequiredEvent().Set(); + } + + public override void SetVolume(float volume) + { + _volume = volume; + } + + public override void Start() { } + + public override void Stop() { } + + public override void UnregisterBuffer(AudioBuffer buffer) { } + + public override bool WasBufferFullyConsumed(AudioBuffer buffer) + { + return true; + } + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/Common/AudioBuffer.cs b/Ryujinx.Audio/Common/AudioBuffer.cs new file mode 100644 index 00000000..853896d5 --- /dev/null +++ b/Ryujinx.Audio/Common/AudioBuffer.cs @@ -0,0 +1,53 @@ +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Integration; + +namespace Ryujinx.Audio.Common +{ + /// + /// Represent an audio buffer that will be used by an . + /// + public class AudioBuffer + { + /// + /// Unique tag of this buffer. + /// + /// Unique per session + public ulong BufferTag; + + /// + /// Pointer to the user samples. + /// + public ulong DataPointer; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// The timestamp at which the buffer was played. + /// + /// Not used but useful for debugging + public ulong PlayedTimestamp; + + /// + /// The user samples. + /// + public byte[] Data; + } +} diff --git a/Ryujinx.Audio/Common/AudioDeviceSession.cs b/Ryujinx.Audio/Common/AudioDeviceSession.cs new file mode 100644 index 00000000..fbdb5a75 --- /dev/null +++ b/Ryujinx.Audio/Common/AudioDeviceSession.cs @@ -0,0 +1,532 @@ +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Integration; +using Ryujinx.Common; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Common +{ + /// + /// An audio device session. + /// + class AudioDeviceSession : IDisposable + { + /// + /// The volume of the . + /// + private float _volume; + + /// + /// The state of the . + /// + private AudioDeviceState _state; + + /// + /// Array of all buffers currently used or released. + /// + private AudioBuffer[] _buffers; + + /// + /// The server index inside (appended but not queued to device driver). + /// + private uint _serverBufferIndex; + + /// + /// The hardware index inside (queued to device driver). + /// + private uint _hardwareBufferIndex; + + /// + /// The released index inside (released by the device driver). + /// + private uint _releasedBufferIndex; + + /// + /// The count of buffer appended (server side). + /// + private uint _bufferAppendedCount; + + /// + /// The count of buffer registered (driver side). + /// + private uint _bufferRegisteredCount; + + /// + /// The count of buffer released (released by the driver side). + /// + private uint _bufferReleasedCount; + + /// + /// The released buffer event. + /// + private IWritableEvent _bufferEvent; + + /// + /// The session on the device driver. + /// + private IHardwareDeviceSession _hardwareDeviceSession; + + /// + /// Max number of buffers that can be registered to the device driver at a time. + /// + private uint _bufferRegisteredLimit; + + /// + /// Create a new . + /// + /// The device driver session associated + /// The release buffer event + /// The max number of buffers that can be registered to the device driver at a time + public AudioDeviceSession(IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent, uint bufferRegisteredLimit = 4) + { + _bufferEvent = bufferEvent; + _hardwareDeviceSession = deviceSession; + _bufferRegisteredLimit = bufferRegisteredLimit; + + _buffers = new AudioBuffer[Constants.AudioDeviceBufferCountMax]; + _serverBufferIndex = 0; + _hardwareBufferIndex = 0; + _releasedBufferIndex = 0; + + _bufferAppendedCount = 0; + _bufferRegisteredCount = 0; + _bufferReleasedCount = 0; + _volume = 1.0f; + _state = AudioDeviceState.Stopped; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent GetBufferEvent() + { + return _bufferEvent; + } + + /// + /// Get the state of the session. + /// + /// The state of the session + public AudioDeviceState GetState() + { + Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped); + + return _state; + } + + /// + /// Get the total buffer count (server + driver + released). + /// + /// Return the total buffer count + private uint GetTotalBufferCount() + { + uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount; + + Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax); + + return bufferCount; + } + + /// + /// Register a new on the server side. + /// + /// The to register + /// True if the operation succeeded + private bool RegisterBuffer(AudioBuffer buffer) + { + if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _buffers[_serverBufferIndex] = buffer; + _serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount++; + + return true; + } + + /// + /// Flush server buffers to hardware. + /// + private void FlushToHardware() + { + uint bufferToFlushCount = Math.Min(Math.Min(_bufferAppendedCount, 4), _bufferRegisteredLimit - _bufferRegisteredCount); + + AudioBuffer[] buffersToFlush = new AudioBuffer[bufferToFlushCount]; + + uint hardwareBufferIndex = _hardwareBufferIndex; + + for (int i = 0; i < buffersToFlush.Length; i++) + { + buffersToFlush[i] = _buffers[_hardwareBufferIndex]; + + _bufferAppendedCount--; + _bufferRegisteredCount++; + + hardwareBufferIndex = (hardwareBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + _hardwareBufferIndex = hardwareBufferIndex; + + for (int i = 0; i < buffersToFlush.Length; i++) + { + _hardwareDeviceSession.QueueBuffer(buffersToFlush[i]); + } + } + + /// + /// Get the current index of the playing on the driver side. + /// + /// The output index of the playing on the driver side + /// True if any buffer is playing + private bool TryGetPlayingBufferIndex(out uint playingIndex) + { + if (_bufferRegisteredCount > 0) + { + playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + return true; + } + + playingIndex = 0; + + return false; + } + + /// + /// Try to pop the playing on the driver side. + /// + /// The output playing on the driver side + /// True if any buffer is playing + private bool TryPopPlayingBuffer(out AudioBuffer buffer) + { + if (_bufferRegisteredCount > 0) + { + uint bufferIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + buffer = _buffers[bufferIndex]; + + _buffers[bufferIndex] = null; + + _bufferRegisteredCount--; + + return true; + } + + buffer = null; + + return false; + } + + /// + /// Try to pop a released by the driver side. + /// + /// The output released by the driver side + /// True if any buffer has been released + public bool TryPopReleasedBuffer(out AudioBuffer buffer) + { + if (_bufferReleasedCount > 0) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + buffer = _buffers[bufferIndex]; + + _buffers[bufferIndex] = null; + + _bufferReleasedCount--; + + return true; + } + + buffer = null; + + return false; + } + + /// + /// Release a . + /// + /// The to release + private void ReleaseBuffer(AudioBuffer buffer) + { + buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds; + + _bufferRegisteredCount--; + _bufferReleasedCount++; + + _releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + /// + /// Update the released buffers. + /// + /// True if the session is currently stopping + private void UpdateReleaseBuffers(bool updateForStop = false) + { + bool wasAnyBuffersReleased = false; + + while (TryGetPlayingBufferIndex(out uint playingIndex)) + { + if (!updateForStop && !_hardwareDeviceSession.WasBufferFullyConsumed(_buffers[playingIndex])) + { + break; + } + + if (updateForStop) + { + _hardwareDeviceSession.UnregisterBuffer(_buffers[playingIndex]); + } + + ReleaseBuffer(_buffers[playingIndex]); + + wasAnyBuffersReleased = true; + } + + if (wasAnyBuffersReleased) + { + _bufferEvent.Signal(); + } + } + + /// + /// Append a new . + /// + /// The to append + /// True if the buffer was appended + public bool AppendBuffer(AudioBuffer buffer) + { + if (_hardwareDeviceSession.RegisterBuffer(buffer)) + { + if (RegisterBuffer(buffer)) + { + FlushToHardware(); + + return true; + } + + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + return false; + } + + public bool AppendUacBuffer(AudioBuffer buffer, uint handle) + { + // NOTE: On hardware, there is another RegisterBuffer method taking an handle. + // This variant of the call always return false (stubbed?) as a result this logic will never succeed. + + return false; + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + if (_state == AudioDeviceState.Started) + { + return ResultCode.OperationFailed; + } + + _hardwareDeviceSession.Start(); + + _state = AudioDeviceState.Started; + + FlushToHardware(); + + _hardwareDeviceSession.SetVolume(_volume); + + return ResultCode.Success; + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.Stop(); + + UpdateReleaseBuffers(true); + + _state = AudioDeviceState.Stopped; + } + + return ResultCode.Success; + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + return _hardwareDeviceSession.GetVolume(); + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + _volume = volume; + + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + return _bufferAppendedCount + _bufferRegisteredCount; + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + for (int i = 0; i < GetTotalBufferCount(); i++) + { + if (_buffers[bufferIndex].BufferTag == bufferTag) + { + return true; + } + + bufferIndex = (bufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + return false; + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + if (_state == AudioDeviceState.Stopped) + { + return 0; + } + else + { + return _hardwareDeviceSession.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffer was flushed + public bool FlushBuffers() + { + if (_state == AudioDeviceState.Stopped) + { + return false; + } + + uint bufferCount = GetBufferCount(); + + while (TryPopReleasedBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + while (TryPopPlayingBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + if (_bufferRegisteredCount == 0 || (_bufferReleasedCount + _bufferAppendedCount) > Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _bufferReleasedCount += _bufferAppendedCount; + _releasedBufferIndex = (_releasedBufferIndex + _bufferAppendedCount) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount = 0; + _hardwareBufferIndex = _serverBufferIndex; + + if (bufferCount > 0) + { + _bufferEvent.Signal(); + } + + return true; + } + + /// + /// Update the session. + /// + public void Update() + { + if (_state == AudioDeviceState.Started) + { + UpdateReleaseBuffers(); + FlushToHardware(); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Tell the hardware session that we are ending. + _hardwareDeviceSession.PrepareToClose(); + + // Unregister all buffers + + while (TryPopReleasedBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + while (TryPopPlayingBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + // Finally dispose hardware session. + _hardwareDeviceSession.Dispose(); + + _bufferEvent.Signal(); + } + } + } +} diff --git a/Ryujinx.Audio/Common/AudioDeviceState.cs b/Ryujinx.Audio/Common/AudioDeviceState.cs new file mode 100644 index 00000000..77604525 --- /dev/null +++ b/Ryujinx.Audio/Common/AudioDeviceState.cs @@ -0,0 +1,35 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio device state. + /// + public enum AudioDeviceState : uint + { + /// + /// The audio device is started. + /// + Started, + + /// + /// The audio device is stopped. + /// + Stopped + } +} diff --git a/Ryujinx.Audio/Common/AudioInputConfiguration.cs b/Ryujinx.Audio/Common/AudioInputConfiguration.cs new file mode 100644 index 00000000..9aa86569 --- /dev/null +++ b/Ryujinx.Audio/Common/AudioInputConfiguration.cs @@ -0,0 +1,46 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user input configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioInputConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 48000Hz is considered valid, other sample rates will be refused. + public uint SampleRate; + + /// + /// The target channel count of the user. + /// + /// Only Stereo and Surround are considered valid, other configurations will be refused. + /// Not used in audin. + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private ushort _reserved; + } +} diff --git a/Ryujinx.Audio/Common/AudioOutputConfiguration.cs b/Ryujinx.Audio/Common/AudioOutputConfiguration.cs new file mode 100644 index 00000000..a493561f --- /dev/null +++ b/Ryujinx.Audio/Common/AudioOutputConfiguration.cs @@ -0,0 +1,54 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio system output configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioOutputConfiguration + { + /// + /// The target sample rate of the system. + /// + public uint SampleRate; + + /// + /// The target channel count of the system. + /// + public uint ChannelCount; + + /// + /// Reserved/unused + /// + public SampleFormat SampleFormat; + + /// + /// Reserved/unused. + /// + private Array3 _padding; + + /// + /// The initial audio system state. + /// + public AudioDeviceState AudioOutState; + } +} diff --git a/Ryujinx.Audio/Common/AudioUserBuffer.cs b/Ryujinx.Audio/Common/AudioUserBuffer.cs new file mode 100644 index 00000000..a0614184 --- /dev/null +++ b/Ryujinx.Audio/Common/AudioUserBuffer.cs @@ -0,0 +1,53 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user buffer. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioUserBuffer + { + /// + /// Pointer to the next buffer (ignored). + /// + public ulong NextBuffer; + + /// + /// Pointer to the user samples. + /// + public ulong Data; + + /// + /// Capacity of the buffer (unused). + /// + public ulong Capacity; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// Offset in the user samples region (unused). + /// + public ulong DataOffset; + } +} diff --git a/Ryujinx.Audio.Renderer/Common/SampleFormat.cs b/Ryujinx.Audio/Common/SampleFormat.cs similarity index 97% rename from Ryujinx.Audio.Renderer/Common/SampleFormat.cs rename to Ryujinx.Audio/Common/SampleFormat.cs index b5268408..bb51bdd3 100644 --- a/Ryujinx.Audio.Renderer/Common/SampleFormat.cs +++ b/Ryujinx.Audio/Common/SampleFormat.cs @@ -15,7 +15,7 @@ // along with this program. If not, see . // -namespace Ryujinx.Audio.Renderer.Common +namespace Ryujinx.Audio.Common { /// /// Sample format definition. diff --git a/Ryujinx.Audio.Renderer/RendererConstants.cs b/Ryujinx.Audio/Constants.cs similarity index 76% rename from Ryujinx.Audio.Renderer/RendererConstants.cs rename to Ryujinx.Audio/Constants.cs index d2697378..7a407fc6 100644 --- a/Ryujinx.Audio.Renderer/RendererConstants.cs +++ b/Ryujinx.Audio/Constants.cs @@ -15,13 +15,23 @@ // along with this program. If not, see . // -namespace Ryujinx.Audio.Renderer +namespace Ryujinx.Audio { /// - /// Define constants used by the Audio Renderer. + /// Define constants used by the audio system. /// - public static class RendererConstants + public static class Constants { + /// + /// The default device output name. + /// + public const string DefaultDeviceOutputName = "DeviceOut"; + + /// + /// The default device input name. + /// + public const string DefaultDeviceInputName = "BuiltInHeadset"; + /// /// The maximum number of channels supported. (6 channels for 5.1 surround) /// @@ -33,17 +43,17 @@ namespace Ryujinx.Audio.Renderer public const int VoiceChannelCountMax = ChannelCountMax; /// - /// The max count of mix buffer supported per operations (volumes, mix effect, ...) + /// The maximum count of mix buffer supported per operations (volumes, mix effect, ...) /// public const int MixBufferCountMax = 24; /// - /// The max count of wavebuffer per voice. + /// The maximum count of wavebuffer per voice. /// public const int VoiceWaveBufferCount = 4; /// - /// The max count of biquad filter per voice. + /// The maximum count of biquad filter per voice. /// public const int VoiceBiquadFilterCount = 2; @@ -59,7 +69,7 @@ namespace Ryujinx.Audio.Renderer public const int VoiceHighestPriority = 0; /// - /// Max that can be returned by . + /// Maximum that can be returned by . /// public const int MaxErrorInfos = 10; @@ -109,10 +119,25 @@ namespace Ryujinx.Audio.Renderer public const int InvalidProcessingOrder = -1; /// - /// The max number of audio renderer sessions allowed to be created system wide. + /// The maximum number of audio renderer sessions allowed to be created system wide. /// public const int AudioRendererSessionCountMax = 2; + /// + /// The maximum number of audio output sessions allowed to be created system wide. + /// + public const int AudioOutSessionCountMax = 12; + + /// + /// The maximum number of audio input sessions allowed to be created system wide. + /// + public const int AudioInSessionCountMax = 4; + + /// + /// Maximum buffers supported by one audio device session. + /// + public const int AudioDeviceBufferCountMax = 32; + /// /// The target sample rate of the audio renderer. (48kHz) /// @@ -139,12 +164,12 @@ namespace Ryujinx.Audio.Renderer public const int AudioProcessorMaxUpdateTimeTarget = 1000000000 / ((int)TargetSampleRate / TargetSampleCount); // 5.00 ms /// - /// The max update time of the DSP on original hardware. + /// The maximum update time of the DSP on original hardware. /// public const int AudioProcessorMaxUpdateTime = 5760000; // 5.76 ms /// - /// The max update time per audio renderer session. + /// The maximum update time per audio renderer session. /// public const int AudioProcessorMaxUpdateTimePerSessions = AudioProcessorMaxUpdateTime / AudioRendererSessionCountMax; diff --git a/Ryujinx.Audio/IAalOutput.cs b/Ryujinx.Audio/IAalOutput.cs deleted file mode 100644 index ddb1492c..00000000 --- a/Ryujinx.Audio/IAalOutput.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; - -namespace Ryujinx.Audio -{ - public interface IAalOutput : IDisposable - { - bool SupportsChannelCount(int channels); - - private int SelectHardwareChannelCount(int targetChannelCount) - { - if (SupportsChannelCount(targetChannelCount)) - { - return targetChannelCount; - } - - return targetChannelCount switch - { - 6 => SelectHardwareChannelCount(2), - 2 => SelectHardwareChannelCount(1), - 1 => throw new ArgumentException("No valid channel configuration found!"), - _ => throw new ArgumentException($"Invalid targetChannelCount {targetChannelCount}"), - }; - } - - int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) - { - return OpenHardwareTrack(sampleRate, SelectHardwareChannelCount(channels), channels, callback); - } - - int OpenHardwareTrack(int sampleRate, int hardwareChannels, int virtualChannels, ReleaseCallback callback); - - void CloseTrack(int trackId); - - bool ContainsBuffer(int trackId, long bufferTag); - - long[] GetReleasedBuffers(int trackId, int maxCount); - - void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct; - - void Start(int trackId); - - void Stop(int trackId); - - uint GetBufferCount(int trackId); - - ulong GetPlayedSampleCount(int trackId); - - bool FlushBuffers(int trackId); - - float GetVolume(int trackId); - - void SetVolume(int trackId, float volume); - - PlaybackState GetState(int trackId); - } -} \ No newline at end of file diff --git a/Ryujinx.Audio/Input/AudioInputManager.cs b/Ryujinx.Audio/Input/AudioInputManager.cs new file mode 100644 index 00000000..e098ae9e --- /dev/null +++ b/Ryujinx.Audio/Input/AudioInputManager.cs @@ -0,0 +1,262 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Input +{ + /// + /// The audio input manager. + /// + public class AudioInputManager : IDisposable + { + private object _lock = new object(); + + /// + /// Lock used for session allocation. + /// + private object _sessionLock = new object(); + + /// + /// 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 AudioInputSystem[] _sessions; + + /// + /// The count of active sessions. + /// + private int _activeSessionCount; + + /// + /// Create a new . + /// + public AudioInputManager() + { + _sessionIds = new int[Constants.AudioInSessionCountMax]; + _sessions = new AudioInputSystem[Constants.AudioInSessionCountMax]; + _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 input ({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 input ({sessionId})"); + } + + /// + /// Used to update audio input system. + /// + public void Update() + { + lock (_sessionLock) + { + foreach (AudioInputSystem input in _sessions) + { + input?.Update(); + } + } + } + + /// + /// Register a new . + /// + /// The to register. + private void Register(AudioInputSystem input) + { + lock (_sessionLock) + { + _sessions[input.GetSessionId()] = input; + } + } + + /// + /// Unregister a new . + /// + /// The to unregister. + internal void Unregister(AudioInputSystem input) + { + lock (_sessionLock) + { + int sessionId = input.GetSessionId(); + + _sessions[input.GetSessionId()] = null; + + ReleaseSessionId(sessionId); + } + } + + /// + /// Get the list of all audio inputs names. + /// + /// If true, filter disconnected devices + /// The list of all audio inputs name + public string[] ListAudioIns(bool filtered) + { + if (filtered) + { + // TODO: Detect if the driver supports audio input + } + + return new string[] { Constants.DefaultDeviceInputName }; + } + + /// + /// 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 OpenAudioIn(out string outputDeviceName, + out AudioOutputConfiguration outputConfiguration, + out AudioInputSystem obj, + IVirtualMemoryManager memoryManager, + string inputDeviceName, + SampleFormat sampleFormat, + ref AudioInputConfiguration parameter, + ulong appletResourceUserId, + uint processHandle) + { + int sessionId = AcquireSessionId(); + + _sessionsBufferEvents[sessionId].Clear(); + + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Input, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount); + + AudioInputSystem audioIn = new AudioInputSystem(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]); + + ResultCode result = audioIn.Initialize(inputDeviceName, sampleFormat, ref parameter, sessionId); + + if (result == ResultCode.Success) + { + outputDeviceName = audioIn.DeviceName; + outputConfiguration = new AudioOutputConfiguration + { + ChannelCount = audioIn.ChannelCount, + SampleFormat = audioIn.SampleFormat, + SampleRate = audioIn.SampleRate, + AudioOutState = audioIn.GetState(), + }; + + obj = audioIn; + + Register(audioIn); + } + else + { + ReleaseSessionId(sessionId); + + obj = null; + outputDeviceName = null; + outputConfiguration = default; + } + + return result; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Nothing to do here. + } + } + } +} diff --git a/Ryujinx.Audio/Input/AudioInputSystem.cs b/Ryujinx.Audio/Input/AudioInputSystem.cs new file mode 100644 index 00000000..8064a947 --- /dev/null +++ b/Ryujinx.Audio/Input/AudioInputSystem.cs @@ -0,0 +1,400 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using System; + +namespace Ryujinx.Audio.Input +{ + /// + /// Audio input system. + /// + public class AudioInputSystem : IDisposable + { + /// + /// The session id associated to the . + /// + private int _sessionId; + + /// + /// The session the . + /// + private AudioDeviceSession _session; + + /// + /// The target device name of the . + /// + public string DeviceName { get; private set; } + + /// + /// The target sample rate of the . + /// + public uint SampleRate { get; private set; } + + /// + /// The target channel count of the . + /// + public uint ChannelCount { get; private set; } + + /// + /// The target sample format of the . + /// + public SampleFormat SampleFormat { get; private set; } + + /// + /// The owning this. + /// + private AudioInputManager _manager; + + /// + /// THe lock of the parent. + /// + private object _parentLock; + + /// + /// Create a new . + /// + /// The manager instance + /// The lock of the manager + /// The hardware device session + /// The buffer release event of the audio input + public AudioInputSystem(AudioInputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) + { + _manager = manager; + _parentLock = parentLock; + _session = new AudioDeviceSession(deviceSession, bufferEvent); + } + + /// + /// Get the default device name on the system. + /// + /// The default device name on the system. + private static string GetDeviceDefaultName() + { + return Constants.DefaultDeviceInputName; + } + + /// + /// Check if a given configuration and device name is valid on the system. + /// + /// The configuration to check. + /// The device name to check. + /// A reporting an error or a success. + private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) + { + if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) + { + return ResultCode.DeviceNotFound; + } + else if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) + { + return ResultCode.UnsupportedSampleRate; + } + else if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) + { + return ResultCode.UnsupportedChannelConfiguration; + } + + return ResultCode.Success; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent RegisterBufferEvent() + { + lock (_parentLock) + { + return _session.GetBufferEvent(); + } + } + + /// + /// Update the . + /// + public void Update() + { + lock (_parentLock) + { + _session.Update(); + } + } + + /// + /// Get the id of this session. + /// + /// The id of this session + public int GetSessionId() + { + return _sessionId; + } + + /// + /// Initialize the . + /// + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The session id associated to this + /// A reporting an error or a success. + public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) + { + _sessionId = sessionId; + + ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); + + if (result == ResultCode.Success) + { + if (inputDeviceName.Length == 0) + { + DeviceName = GetDeviceDefaultName(); + } + else + { + DeviceName = inputDeviceName; + } + + if (parameter.ChannelCount == 6) + { + ChannelCount = 6; + } + else + { + ChannelCount = 2; + } + + SampleFormat = sampleFormat; + SampleRate = Constants.TargetSampleRate; + } + + return result; + } + + /// + /// Append a new audio buffer to the audio input. + /// + /// The unique tag of this buffer. + /// The buffer informations. + /// A reporting an error or a success. + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) + { + lock (_parentLock) + { + AudioBuffer buffer = new AudioBuffer + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize + }; + + if (_session.AppendBuffer(buffer)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Append a new audio buffer to the audio input. + /// + /// This is broken by design, only added for completness. + /// The unique tag of this buffer. + /// The buffer informations. + /// Some unknown handle. + /// A reporting an error or a success. + public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer, uint handle) + { + lock (_parentLock) + { + AudioBuffer buffer = new AudioBuffer + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize + }; + + if (_session.AppendUacBuffer(buffer, handle)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Get the release buffers. + /// + /// The buffer to write the release buffers + /// The count of released buffers + /// A reporting an error or a success. + public ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount) + { + releasedCount = 0; + + // Ensure that the first entry is set to zero if no entries are returned. + if (releasedBuffers.Length > 0) + { + releasedBuffers[0] = 0; + } + + lock (_parentLock) + { + for (int i = 0; i < releasedBuffers.Length; i++) + { + if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) + { + break; + } + + releasedBuffers[i] = buffer.BufferTag; + releasedCount++; + } + } + + return ResultCode.Success; + } + + /// + /// Get the current state of the . + /// + /// Return the curent sta\te of the + public AudioDeviceState GetState() + { + lock (_parentLock) + { + return _session.GetState(); + } + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + lock (_parentLock) + { + return _session.Start(); + } + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + lock (_parentLock) + { + return _session.Stop(); + } + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + lock (_parentLock) + { + return _session.GetVolume(); + } + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + lock (_parentLock) + { + _session.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + lock (_parentLock) + { + return _session.GetBufferCount(); + } + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + lock (_parentLock) + { + return _session.ContainsBuffer(bufferTag); + } + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + lock (_parentLock) + { + return _session.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffers was flushed + public bool FlushBuffers() + { + lock (_parentLock) + { + return _session.FlushBuffers(); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + + _manager.Unregister(this); + } + } + } +} diff --git a/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs b/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs new file mode 100644 index 00000000..d489b008 --- /dev/null +++ b/Ryujinx.Audio/Integration/HardwareDeviceImpl.cs @@ -0,0 +1,82 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Integration +{ + public class HardwareDeviceImpl : IHardwareDevice + { + private IHardwareDeviceSession _session; + private uint _channelCount; + private uint _sampleRate; + private uint _currentBufferTag; + + private byte[] _buffer; + + public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate) + { + _session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount); + _channelCount = channelCount; + _sampleRate = sampleRate; + _currentBufferTag = 0; + + _buffer = new byte[Constants.TargetSampleCount * channelCount * sizeof(ushort)]; + + _session.Start(); + } + + public void AppendBuffer(ReadOnlySpan data, uint channelCount) + { + data.CopyTo(MemoryMarshal.Cast(_buffer)); + + _session.QueueBuffer(new AudioBuffer + { + DataPointer = _currentBufferTag++, + Data = _buffer, + DataSize = (ulong)_buffer.Length, + }); + + _currentBufferTag = _currentBufferTag % 4; + } + + public uint GetChannelCount() + { + return _channelCount; + } + + public uint GetSampleRate() + { + return _sampleRate; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + } + } + } +} diff --git a/Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs b/Ryujinx.Audio/Integration/IHardwareDevice.cs similarity index 83% rename from Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs rename to Ryujinx.Audio/Integration/IHardwareDevice.cs index 6a82ec85..0f67b2c8 100644 --- a/Ryujinx.Audio.Renderer/Integration/HardwareDevice.cs +++ b/Ryujinx.Audio/Integration/IHardwareDevice.cs @@ -18,12 +18,12 @@ using System; using System.Diagnostics; -namespace Ryujinx.Audio.Renderer.Integration +namespace Ryujinx.Audio.Integration { /// - /// Represent an hardware device used in + /// Represent an hardware device used in /// - public interface HardwareDevice : IDisposable + public interface IHardwareDevice : IDisposable { /// /// Get the supported sample rate of this device. @@ -52,9 +52,9 @@ namespace Ryujinx.Audio.Renderer.Integration { uint channelCount = GetChannelCount(); - Debug.Assert(channelCount > 0 && channelCount <= RendererConstants.ChannelCountMax); + Debug.Assert(channelCount > 0 && channelCount <= Constants.ChannelCountMax); - return channelCount != RendererConstants.ChannelCountMax; + return channelCount != Constants.ChannelCountMax; } } } diff --git a/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs b/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs new file mode 100644 index 00000000..70738c90 --- /dev/null +++ b/Ryujinx.Audio/Integration/IHardwareDeviceDriver.cs @@ -0,0 +1,50 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using Ryujinx.Memory; +using System; +using System.Threading; + +namespace Ryujinx.Audio.Integration +{ + /// + /// Represent an hardware device driver used in . + /// + public interface IHardwareDeviceDriver : IDisposable + { + public enum Direction + { + Input, + Output + } + + IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount); + + ManualResetEvent GetUpdateRequiredEvent(); + + bool SupportsDirection(Direction direction); + bool SupportsSampleRate(uint sampleRate); + bool SupportsSampleFormat(SampleFormat sampleFormat); + bool SupportsChannelCount(uint channelCount); + + IHardwareDeviceDriver GetRealDeviceDriver() + { + return this; + } + } +} diff --git a/Ryujinx.Audio.Renderer/ResultCode.cs b/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs similarity index 57% rename from Ryujinx.Audio.Renderer/ResultCode.cs rename to Ryujinx.Audio/Integration/IHardwareDeviceSession.cs index 9b11aefa..0e286768 100644 --- a/Ryujinx.Audio.Renderer/ResultCode.cs +++ b/Ryujinx.Audio/Integration/IHardwareDeviceSession.cs @@ -1,4 +1,4 @@ -// +// // Copyright (c) 2019-2021 Ryujinx // // This program is free software: you can redistribute it and/or modify @@ -15,20 +15,31 @@ // along with this program. If not, see . // -namespace Ryujinx.Audio.Renderer +using Ryujinx.Audio.Common; +using System; + +namespace Ryujinx.Audio.Integration { - public enum ResultCode + public interface IHardwareDeviceSession : IDisposable { - ModuleId = 153, - ErrorCodeShift = 9, + bool RegisterBuffer(AudioBuffer buffer); - Success = 0, + void UnregisterBuffer(AudioBuffer buffer); - OperationFailed = (2 << ErrorCodeShift) | ModuleId, - WorkBufferTooSmall = (4 << ErrorCodeShift) | ModuleId, - InvalidUpdateInfo = (41 << ErrorCodeShift) | ModuleId, - InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId, - InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId, - UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId, + void QueueBuffer(AudioBuffer buffer); + + bool WasBufferFullyConsumed(AudioBuffer buffer); + + void SetVolume(float volume); + + float GetVolume(); + + ulong GetPlayedSampleCount(); + + void Start(); + + void Stop(); + + void PrepareToClose(); } } diff --git a/Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs b/Ryujinx.Audio/Integration/IWritableEvent.cs similarity index 95% rename from Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs rename to Ryujinx.Audio/Integration/IWritableEvent.cs index 22864b74..ced20dbd 100644 --- a/Ryujinx.Audio.Renderer/Integration/IWritableEvent.cs +++ b/Ryujinx.Audio/Integration/IWritableEvent.cs @@ -15,7 +15,7 @@ // along with this program. If not, see . // -namespace Ryujinx.Audio.Renderer.Integration +namespace Ryujinx.Audio.Integration { /// /// Represent a writable event with manual clear. diff --git a/Ryujinx.Audio.Renderer/LICENSE.txt b/Ryujinx.Audio/LICENSE.txt similarity index 100% rename from Ryujinx.Audio.Renderer/LICENSE.txt rename to Ryujinx.Audio/LICENSE.txt diff --git a/Ryujinx.Audio/Output/AudioOutputManager.cs b/Ryujinx.Audio/Output/AudioOutputManager.cs new file mode 100644 index 00000000..baa84997 --- /dev/null +++ b/Ryujinx.Audio/Output/AudioOutputManager.cs @@ -0,0 +1,256 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Common.Logging; +using Ryujinx.Memory; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Output +{ + /// + /// The audio output manager. + /// + public class AudioOutputManager : IDisposable + { + private object _lock = new object(); + + /// + /// Lock used for session allocation. + /// + private object _sessionLock = new object(); + + /// + /// 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; + + /// + /// 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) + { + int sessionId = AcquireSessionId(); + + _sessionsBufferEvents[sessionId].Clear(); + + IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount); + + 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; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Nothing to do here. + } + } + } +} diff --git a/Ryujinx.Audio/Output/AudioOutputSystem.cs b/Ryujinx.Audio/Output/AudioOutputSystem.cs new file mode 100644 index 00000000..f5db9d7a --- /dev/null +++ b/Ryujinx.Audio/Output/AudioOutputSystem.cs @@ -0,0 +1,373 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using System; + +namespace Ryujinx.Audio.Output +{ + /// + /// Audio output system. + /// + public class AudioOutputSystem : IDisposable + { + /// + /// The session id associated to the . + /// + private int _sessionId; + + /// + /// The session the . + /// + private AudioDeviceSession _session; + + /// + /// The target device name of the . + /// + public string DeviceName { get; private set; } + + /// + /// The target sample rate of the . + /// + public uint SampleRate { get; private set; } + + /// + /// The target channel count of the . + /// + public uint ChannelCount { get; private set; } + + /// + /// The target sample format of the . + /// + public SampleFormat SampleFormat { get; private set; } + + /// + /// The owning this. + /// + private AudioOutputManager _manager; + + /// + /// THe lock of the parent. + /// + private object _parentLock; + + /// + /// Create a new . + /// + /// The manager instance + /// The lock of the manager + /// The hardware device session + /// The buffer release event of the audio output + public AudioOutputSystem(AudioOutputManager manager, object parentLock, IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent) + { + _manager = manager; + _parentLock = parentLock; + _session = new AudioDeviceSession(deviceSession, bufferEvent); + } + + /// + /// Get the default device name on the system. + /// + /// The default device name on the system. + private static string GetDeviceDefaultName() + { + return Constants.DefaultDeviceOutputName; + } + + /// + /// Check if a given configuration and device name is valid on the system. + /// + /// The configuration to check. + /// The device name to check. + /// A reporting an error or a success. + private static ResultCode IsConfigurationValid(ref AudioInputConfiguration configuration, string deviceName) + { + if (deviceName.Length != 0 && !deviceName.Equals(GetDeviceDefaultName())) + { + return ResultCode.DeviceNotFound; + } + else if (configuration.SampleRate != 0 && configuration.SampleRate != Constants.TargetSampleRate) + { + return ResultCode.UnsupportedSampleRate; + } + else if (configuration.ChannelCount != 0 && configuration.ChannelCount != 1 && configuration.ChannelCount != 2 && configuration.ChannelCount != 6) + { + return ResultCode.UnsupportedChannelConfiguration; + } + + return ResultCode.Success; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent RegisterBufferEvent() + { + lock (_parentLock) + { + return _session.GetBufferEvent(); + } + } + + /// + /// Update the . + /// + public void Update() + { + lock (_parentLock) + { + _session.Update(); + } + } + + /// + /// Get the id of this session. + /// + /// The id of this session + public int GetSessionId() + { + return _sessionId; + } + + /// + /// Initialize the . + /// + /// The input device name wanted by the user + /// The sample format to use + /// The user configuration + /// The session id associated to this + /// A reporting an error or a success. + public ResultCode Initialize(string inputDeviceName, SampleFormat sampleFormat, ref AudioInputConfiguration parameter, int sessionId) + { + _sessionId = sessionId; + + ResultCode result = IsConfigurationValid(ref parameter, inputDeviceName); + + if (result == ResultCode.Success) + { + if (inputDeviceName.Length == 0) + { + DeviceName = GetDeviceDefaultName(); + } + else + { + DeviceName = inputDeviceName; + } + + if (parameter.ChannelCount == 6) + { + ChannelCount = 6; + } + else + { + ChannelCount = 2; + } + + SampleFormat = sampleFormat; + SampleRate = Constants.TargetSampleRate; + } + + return result; + } + + /// + /// Append a new audio buffer to the audio output. + /// + /// The unique tag of this buffer. + /// The buffer informations. + /// A reporting an error or a success. + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer userBuffer) + { + lock (_parentLock) + { + AudioBuffer buffer = new AudioBuffer + { + BufferTag = bufferTag, + DataPointer = userBuffer.Data, + DataSize = userBuffer.DataSize + }; + + if (_session.AppendBuffer(buffer)) + { + return ResultCode.Success; + } + + return ResultCode.BufferRingFull; + } + } + + /// + /// Get the release buffers. + /// + /// The buffer to write the release buffers + /// The count of released buffers + /// A reporting an error or a success. + public ResultCode GetReleasedBuffer(Span releasedBuffers, out uint releasedCount) + { + releasedCount = 0; + + // Ensure that the first entry is set to zero if no entries are returned. + if (releasedBuffers.Length > 0) + { + releasedBuffers[0] = 0; + } + + lock (_parentLock) + { + for (int i = 0; i < releasedBuffers.Length; i++) + { + if (!_session.TryPopReleasedBuffer(out AudioBuffer buffer)) + { + break; + } + + releasedBuffers[i] = buffer.BufferTag; + releasedCount++; + } + } + + return ResultCode.Success; + } + + /// + /// Get the current state of the . + /// + /// Return the curent sta\te of the + /// + public AudioDeviceState GetState() + { + lock (_parentLock) + { + return _session.GetState(); + } + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + lock (_parentLock) + { + return _session.Start(); + } + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + lock (_parentLock) + { + return _session.Stop(); + } + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + lock (_parentLock) + { + return _session.GetVolume(); + } + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + lock (_parentLock) + { + _session.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + lock (_parentLock) + { + return _session.GetBufferCount(); + } + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + lock (_parentLock) + { + return _session.ContainsBuffer(bufferTag); + } + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + lock (_parentLock) + { + return _session.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffers was flushed + public bool FlushBuffers() + { + lock (_parentLock) + { + return _session.FlushBuffers(); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _session.Dispose(); + + _manager.Unregister(this); + } + } + } +} diff --git a/Ryujinx.Audio/PlaybackState.cs b/Ryujinx.Audio/PlaybackState.cs deleted file mode 100644 index 7d862092..00000000 --- a/Ryujinx.Audio/PlaybackState.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Ryujinx.Audio -{ - /// - /// The playback state of a track - /// - public enum PlaybackState - { - /// - /// The track is currently playing - /// - Playing = 0, - /// - /// The track is currently stopped - /// - Stopped = 1 - } -} \ No newline at end of file diff --git a/Ryujinx.Audio/ReleaseCallback.cs b/Ryujinx.Audio/ReleaseCallback.cs deleted file mode 100644 index f534e02c..00000000 --- a/Ryujinx.Audio/ReleaseCallback.cs +++ /dev/null @@ -1,4 +0,0 @@ -namespace Ryujinx.Audio -{ - public delegate void ReleaseCallback(); -} \ No newline at end of file diff --git a/Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs b/Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/AuxiliaryBufferAddresses.cs rename to Ryujinx.Audio/Renderer/Common/AuxiliaryBufferAddresses.cs diff --git a/Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs b/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/BehaviourParameter.cs rename to Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs diff --git a/Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs b/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs similarity index 98% rename from Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs rename to Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs index 33b61780..7423eda4 100644 --- a/Ryujinx.Audio.Renderer/Common/EdgeMatrix.cs +++ b/Ryujinx.Audio/Renderer/Common/EdgeMatrix.cs @@ -46,7 +46,7 @@ namespace Ryujinx.Audio.Renderer.Common /// The size required for the given . public static int GetWorkBufferSize(int nodeCount) { - int size = BitUtils.AlignUp(nodeCount * nodeCount, RendererConstants.BufferAlignment); + int size = BitUtils.AlignUp(nodeCount * nodeCount, Constants.BufferAlignment); return size / Unsafe.SizeOf(); } diff --git a/Ryujinx.Audio.Renderer/Common/EffectType.cs b/Ryujinx.Audio/Renderer/Common/EffectType.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/EffectType.cs rename to Ryujinx.Audio/Renderer/Common/EffectType.cs diff --git a/Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs b/Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/MemoryPoolUserState.cs rename to Ryujinx.Audio/Renderer/Common/MemoryPoolUserState.cs diff --git a/Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs b/Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/NodeIdHelper.cs rename to Ryujinx.Audio/Renderer/Common/NodeIdHelper.cs diff --git a/Ryujinx.Audio.Renderer/Common/NodeIdType.cs b/Ryujinx.Audio/Renderer/Common/NodeIdType.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/NodeIdType.cs rename to Ryujinx.Audio/Renderer/Common/NodeIdType.cs diff --git a/Ryujinx.Audio.Renderer/Common/NodeStates.cs b/Ryujinx.Audio/Renderer/Common/NodeStates.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/NodeStates.cs rename to Ryujinx.Audio/Renderer/Common/NodeStates.cs diff --git a/Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/PerformanceDetailType.cs rename to Ryujinx.Audio/Renderer/Common/PerformanceDetailType.cs diff --git a/Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs b/Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/PerformanceEntryType.cs rename to Ryujinx.Audio/Renderer/Common/PerformanceEntryType.cs diff --git a/Ryujinx.Audio.Renderer/Common/PlayState.cs b/Ryujinx.Audio/Renderer/Common/PlayState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/PlayState.cs rename to Ryujinx.Audio/Renderer/Common/PlayState.cs diff --git a/Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs b/Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/ReverbEarlyMode.cs rename to Ryujinx.Audio/Renderer/Common/ReverbEarlyMode.cs diff --git a/Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs b/Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/ReverbLateMode.cs rename to Ryujinx.Audio/Renderer/Common/ReverbLateMode.cs diff --git a/Ryujinx.Audio.Renderer/Common/SinkType.cs b/Ryujinx.Audio/Renderer/Common/SinkType.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/SinkType.cs rename to Ryujinx.Audio/Renderer/Common/SinkType.cs diff --git a/Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs b/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/UpdateDataHeader.cs rename to Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs diff --git a/Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs b/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs rename to Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs index b455ff5c..6cebad5e 100644 --- a/Ryujinx.Audio.Renderer/Common/VoiceUpdateState.cs +++ b/Ryujinx.Audio/Renderer/Common/VoiceUpdateState.cs @@ -86,7 +86,7 @@ namespace Ryujinx.Audio.Renderer.Common /// public int LoopCount; - [StructLayout(LayoutKind.Sequential, Size = 1 * RendererConstants.VoiceWaveBufferCount, Pack = 1)] + [StructLayout(LayoutKind.Sequential, Size = 1 * Constants.VoiceWaveBufferCount, Pack = 1)] private struct WaveBufferValidArray { } /// @@ -107,7 +107,7 @@ namespace Ryujinx.Audio.Renderer.Common LoopCount = 0; waveBufferConsumed++; - if (waveBufferIndex >= RendererConstants.VoiceWaveBufferCount) + if (waveBufferIndex >= Constants.VoiceWaveBufferCount) { waveBufferIndex = 0; } diff --git a/Ryujinx.Audio.Renderer/Common/WaveBuffer.cs b/Ryujinx.Audio/Renderer/Common/WaveBuffer.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/WaveBuffer.cs rename to Ryujinx.Audio/Renderer/Common/WaveBuffer.cs diff --git a/Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs b/Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Common/WorkBufferAllocator.cs rename to Ryujinx.Audio/Renderer/Common/WorkBufferAllocator.cs diff --git a/Ryujinx.Audio.Renderer/Device/VirtualDevice.cs b/Ryujinx.Audio/Renderer/Device/VirtualDevice.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Device/VirtualDevice.cs rename to Ryujinx.Audio/Renderer/Device/VirtualDevice.cs diff --git a/Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs b/Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Device/VirtualDeviceSession.cs rename to Ryujinx.Audio/Renderer/Device/VirtualDeviceSession.cs diff --git a/Ryujinx.Audio.Renderer/Device/VirtualDeviceSessionRegistry.cs b/Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Device/VirtualDeviceSessionRegistry.cs rename to Ryujinx.Audio/Renderer/Device/VirtualDeviceSessionRegistry.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs b/Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/AdpcmHelper.cs rename to Ryujinx.Audio/Renderer/Dsp/AdpcmHelper.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs b/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs similarity index 79% rename from Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs rename to Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs index d6cb497a..2c97a0c1 100644 --- a/Ryujinx.Audio.Renderer/Dsp/AudioProcessor.cs +++ b/Ryujinx.Audio/Renderer/Dsp/AudioProcessor.cs @@ -15,8 +15,8 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Dsp.Command; -using Ryujinx.Audio.Renderer.Integration; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common; using Ryujinx.Common.Logging; @@ -48,7 +48,8 @@ namespace Ryujinx.Audio.Renderer.Dsp private Mailbox _mailbox; private RendererSession[] _sessionCommandList; private Thread _workerThread; - private HardwareDevice[] _outputDevices; + + public IHardwareDevice[] OutputDevices { get; private set; } private long _lastTime; private long _playbackEnds; @@ -59,15 +60,38 @@ namespace Ryujinx.Audio.Renderer.Dsp _event = new ManualResetEvent(false); } - public void SetOutputDevices(HardwareDevice[] outputDevices) + private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver) { - _outputDevices = outputDevices; + // Get the real device driver (In case the compat layer is on top of it). + deviceDriver = deviceDriver.GetRealDeviceDriver(); + + if (deviceDriver.SupportsChannelCount(6)) + { + return 6; + } + else + { + // NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible. + return 2; + } } - public void Start() + public void Start(IHardwareDeviceDriver deviceDriver) { + OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax]; + + // TODO: Before enabling this, we need up-mixing from stereo to 5.1. + // uint channelCount = GetHardwareChannelCount(deviceDriver); + uint channelCount = 2; + + for (int i = 0; i < OutputDevices.Length; i++) + { + // TODO: Don't hardcode sample rate. + OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate); + } + _mailbox = new Mailbox(); - _sessionCommandList = new RendererSession[RendererConstants.AudioRendererSessionCountMax]; + _sessionCommandList = new RendererSession[Constants.AudioRendererSessionCountMax]; _event.Reset(); _lastTime = PerformanceCounter.ElapsedNanoseconds; @@ -89,6 +113,11 @@ namespace Ryujinx.Audio.Renderer.Dsp { throw new InvalidOperationException("Audio Processor Stop response was invalid!"); } + + foreach (IHardwareDevice device in OutputDevices) + { + device.Dispose(); + } } public void Send(int sessionId, CommandList commands, int renderingLimit, ulong appletResourceId) @@ -113,7 +142,7 @@ namespace Ryujinx.Audio.Renderer.Dsp throw new InvalidOperationException("Audio Processor Wait response was invalid!"); } - long increment = RendererConstants.AudioProcessorMaxUpdateTimeTarget; + long increment = Constants.AudioProcessorMaxUpdateTimeTarget; long timeNow = PerformanceCounter.ElapsedNanoseconds; @@ -188,7 +217,7 @@ namespace Ryujinx.Audio.Renderer.Dsp { if (_sessionCommandList[i] != null) { - _sessionCommandList[i].CommandList.Process(_outputDevices[i]); + _sessionCommandList[i].CommandList.Process(OutputDevices[i]); _sessionCommandList[i] = null; } } @@ -197,9 +226,9 @@ namespace Ryujinx.Audio.Renderer.Dsp long elapsedTime = endTicks - startTicks; - if (RendererConstants.AudioProcessorMaxUpdateTime < elapsedTime) + if (Constants.AudioProcessorMaxUpdateTime < elapsedTime) { - Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - RendererConstants.AudioProcessorMaxUpdateTime}ns)"); + Logger.Debug?.Print(LogClass.AudioRenderer, $"DSP too slow (exceeded by {elapsedTime - Constants.AudioProcessorMaxUpdateTime}ns)"); } _mailbox.SendResponse(MailboxMessage.RenderEnd); diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs index daf75208..bea6215d 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/AdpcmDataSourceCommandVersion1.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using System; using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; @@ -54,7 +55,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command SampleRate = serverState.SampleRate; Pitch = serverState.Pitch; - WaveBuffers = new WaveBuffer[RendererConstants.VoiceWaveBufferCount]; + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; for (int i = 0; i < WaveBuffers.Length; i++) { diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/AuxiliaryBufferCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/AuxiliaryBufferCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/BiquadFilterCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/BiquadFilterCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/CircularBufferSinkCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs similarity index 97% rename from Ryujinx.Audio.Renderer/Dsp/Command/CircularBufferSinkCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs index 509aff27..e4c635d5 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/CircularBufferSinkCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/CircularBufferSinkCommand.cs @@ -44,7 +44,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command Enabled = true; NodeId = nodeId; - Input = new ushort[RendererConstants.ChannelCountMax]; + Input = new ushort[Constants.ChannelCountMax]; InputCount = parameter.InputCount; for (int i = 0; i < InputCount; i++) diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/ClearMixBufferCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/ClearMixBufferCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs index bcc2cd11..fee90192 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/CommandList.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/CommandList.cs @@ -15,7 +15,7 @@ // along with this program. If not, see . // -using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Server; using Ryujinx.Common; using Ryujinx.Common.Logging; @@ -39,7 +39,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command public IVirtualMemoryManager MemoryManager { get; } - public HardwareDevice OutputDevice { get; private set; } + public IHardwareDevice OutputDevice { get; private set; } public CommandList(AudioRenderSystem renderSystem) : this(renderSystem.MemoryManager, renderSystem.GetMixBuffer(), @@ -85,7 +85,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command return (ulong)PerformanceCounter.ElapsedNanoseconds - StartTime; } - public void Process(HardwareDevice outputDevice) + public void Process(IHardwareDevice outputDevice) { OutputDevice = outputDevice; diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/CommandType.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/CommandType.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/CopyMixBufferCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/CopyMixBufferCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs similarity index 97% rename from Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs index 469f6ca5..cbc48be7 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/DataSourceVersion2Command.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/DataSourceVersion2Command.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using System; using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; @@ -67,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command SampleRate = serverState.SampleRate; Pitch = serverState.Pitch; - WaveBuffers = new WaveBuffer[RendererConstants.VoiceWaveBufferCount]; + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; for (int i = 0; i < WaveBuffers.Length; i++) { diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs similarity index 98% rename from Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs index 54234a93..4ceebcf6 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/DelayCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/DelayCommand.cs @@ -54,8 +54,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command IsEffectEnabled = isEnabled; - InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; - OutputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; for (int i = 0; i < Parameter.ChannelCount; i++) { diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/DepopForMixBuffersCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/DepopForMixBuffersCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs similarity index 93% rename from Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs index d09c380e..35275229 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/DepopPrepareCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/DepopPrepareCommand.cs @@ -43,9 +43,9 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command NodeId = nodeId; MixBufferCount = mixBufferCount; - OutputBufferIndices = new ushort[RendererConstants.MixBufferCountMax]; + OutputBufferIndices = new ushort[Constants.MixBufferCountMax]; - for (int i = 0; i < RendererConstants.MixBufferCountMax; i++) + for (int i = 0; i < Constants.MixBufferCountMax; i++) { OutputBufferIndices[i] = (ushort)(bufferOffset + i); } diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs similarity index 92% rename from Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs index 621e0269..28b4a5f1 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/DeviceSinkCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/DeviceSinkCommand.cs @@ -15,7 +15,7 @@ // along with this program. If not, see . // -using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Server.Sink; using System; using System.Runtime.CompilerServices; @@ -75,14 +75,14 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command public void Process(CommandList context) { - HardwareDevice device = context.OutputDevice; + IHardwareDevice device = context.OutputDevice; - if (device.GetSampleRate() == RendererConstants.TargetSampleRate) + if (device.GetSampleRate() == Constants.TargetSampleRate) { int channelCount = (int)device.GetChannelCount(); uint bufferCount = Math.Min(device.GetChannelCount(), InputCount); - const int sampleCount = RendererConstants.TargetSampleCount; + const int sampleCount = Constants.TargetSampleCount; short[] outputBuffer = new short[bufferCount * sampleCount]; diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs similarity index 93% rename from Ryujinx.Audio.Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs index 96138db2..f81a2849 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/DownMixSurroundToStereoCommand.cs @@ -40,10 +40,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command Enabled = true; NodeId = nodeId; - InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; - OutputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; - for (int i = 0; i < RendererConstants.VoiceChannelCountMax; i++) + for (int i = 0; i < Constants.VoiceChannelCountMax; i++) { InputBufferIndices[i] = (ushort)(bufferOffset + inputBufferOffset[i]); OutputBufferIndices[i] = (ushort)(bufferOffset + outputBufferOffset[i]); diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/ICommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/ICommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/MixCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/MixCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/MixRampCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/MixRampCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs similarity index 91% rename from Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs index d45da1e3..cbd2503b 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/MixRampGroupedCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/MixRampGroupedCommand.cs @@ -46,10 +46,10 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command MixBufferCount = mixBufferCount; NodeId = nodeId; - InputBufferIndices = new ushort[RendererConstants.MixBufferCountMax]; - OutputBufferIndices = new ushort[RendererConstants.MixBufferCountMax]; - Volume0 = new float[RendererConstants.MixBufferCountMax]; - Volume1 = new float[RendererConstants.MixBufferCountMax]; + InputBufferIndices = new ushort[Constants.MixBufferCountMax]; + OutputBufferIndices = new ushort[Constants.MixBufferCountMax]; + Volume0 = new float[Constants.MixBufferCountMax]; + Volume1 = new float[Constants.MixBufferCountMax]; for (int i = 0; i < mixBufferCount; i++) { diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs index d50c61fd..c4b9b0ff 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/PcmFloatDataSourceCommandVersion1.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using System; using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; @@ -55,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command ChannelCount = serverState.ChannelsCount; Pitch = serverState.Pitch; - WaveBuffers = new WaveBuffer[RendererConstants.VoiceWaveBufferCount]; + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; for (int i = 0; i < WaveBuffers.Length; i++) { diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs index 9decd431..3758dbe0 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/PcmInt16DataSourceCommandVersion1.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using System; using static Ryujinx.Audio.Renderer.Parameter.VoiceInParameter; @@ -55,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command ChannelCount = serverState.ChannelsCount; Pitch = serverState.Pitch; - WaveBuffers = new WaveBuffer[RendererConstants.VoiceWaveBufferCount]; + WaveBuffers = new WaveBuffer[Constants.VoiceWaveBufferCount]; for (int i = 0; i < WaveBuffers.Length; i++) { diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/PerformanceCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/PerformanceCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs similarity index 98% rename from Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs index c1527758..c501bc1c 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/Reverb3dCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/Reverb3dCommand.cs @@ -71,8 +71,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command State = state; WorkBuffer = workBuffer; - InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; - OutputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; for (int i = 0; i < Parameter.ChannelCount; i++) { @@ -110,7 +110,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command bool isMono = Parameter.ChannelCount == 1; bool isSurround = Parameter.ChannelCount == 6; - float[] outputValues = new float[RendererConstants.ChannelCountMax]; + float[] outputValues = new float[Constants.ChannelCountMax]; float[] channelInput = new float[Parameter.ChannelCount]; float[] feedbackValues = new float[4]; float[] feedbackOutputValues = new float[4]; diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs index f0303ed1..0ed955de 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/ReverbCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/ReverbCommand.cs @@ -41,8 +41,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command private static readonly int[] OutputEarlyIndicesTableSurround = new int[20] { 0, 5, 0, 5, 1, 5, 1, 5, 4, 5, 4, 5, 2, 5, 2, 5, 3, 5, 3, 5 }; private static readonly int[] TargetEarlyDelayLineIndicesTableSurround = new int[20] { 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9 }; - private static readonly int[] OutputIndicesTableSurround = new int[RendererConstants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 }; - private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[RendererConstants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 }; + private static readonly int[] OutputIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, 4, 5 }; + private static readonly int[] TargetOutputFeedbackIndicesTableSurround = new int[Constants.ChannelCountMax] { 0, 1, 2, 3, -1, 3 }; public bool Enabled { get; set; } @@ -74,8 +74,8 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command State = state; WorkBuffer = workBuffer; - InputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; - OutputBufferIndices = new ushort[RendererConstants.VoiceChannelCountMax]; + InputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; + OutputBufferIndices = new ushort[Constants.VoiceChannelCountMax]; for (int i = 0; i < Parameter.ChannelCount; i++) { @@ -141,7 +141,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command float outGain = FixedPointHelper.ToFloat(Parameter.OutGain, FixedPointPrecision); float dryGain = FixedPointHelper.ToFloat(Parameter.DryGain, FixedPointPrecision); - float[] outputValues = new float[RendererConstants.ChannelCountMax]; + float[] outputValues = new float[Constants.ChannelCountMax]; float[] feedbackValues = new float[4]; float[] feedbackOutputValues = new float[4]; float[] channelInput = new float[Parameter.ChannelCount]; diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs similarity index 97% rename from Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs index 3bfaa0f6..6c446f05 100644 --- a/Ryujinx.Audio.Renderer/Dsp/Command/UpsampleCommand.cs +++ b/Ryujinx.Audio/Renderer/Dsp/Command/UpsampleCommand.cs @@ -69,7 +69,7 @@ namespace Ryujinx.Audio.Renderer.Dsp.Command public void Process(CommandList context) { - float ratio = (float)InputSampleRate / RendererConstants.TargetSampleRate; + float ratio = (float)InputSampleRate / Constants.TargetSampleRate; uint bufferCount = Math.Min(BufferCount, UpsamplerInfo.SourceSampleCount); diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/VolumeCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/VolumeCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs b/Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Command/VolumeRampCommand.cs rename to Ryujinx.Audio/Renderer/Dsp/Command/VolumeRampCommand.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs b/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs similarity index 99% rename from Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs rename to Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs index bb583dd3..c9514529 100644 --- a/Ryujinx.Audio.Renderer/Dsp/DataSourceHelper.cs +++ b/Ryujinx.Audio/Renderer/Dsp/DataSourceHelper.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Dsp.State; using Ryujinx.Common.Logging; @@ -111,7 +112,7 @@ namespace Ryujinx.Audio.Renderer.Dsp while (y < sampleCountToDecode) { - if (waveBufferIndex >= RendererConstants.VoiceWaveBufferCount) + if (waveBufferIndex >= Constants.VoiceWaveBufferCount) { Logger.Error?.Print(LogClass.AudioRenderer, $"Invalid WaveBuffer index {waveBufferIndex}"); diff --git a/Ryujinx.Audio.Renderer/Dsp/Effect/DecayDelay.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Effect/DecayDelay.cs rename to Ryujinx.Audio/Renderer/Dsp/Effect/DecayDelay.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Effect/DelayLine.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Effect/DelayLine.cs rename to Ryujinx.Audio/Renderer/Dsp/Effect/DelayLine.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Effect/DelayLineReverb3d.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Effect/DelayLineReverb3d.cs rename to Ryujinx.Audio/Renderer/Dsp/Effect/DelayLineReverb3d.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/Effect/IDelayLine.cs b/Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/Effect/IDelayLine.cs rename to Ryujinx.Audio/Renderer/Dsp/Effect/IDelayLine.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/FixedPointHelper.cs b/Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/FixedPointHelper.cs rename to Ryujinx.Audio/Renderer/Dsp/FixedPointHelper.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/FloatingPointHelper.cs b/Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/FloatingPointHelper.cs rename to Ryujinx.Audio/Renderer/Dsp/FloatingPointHelper.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs b/Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/PcmHelper.cs rename to Ryujinx.Audio/Renderer/Dsp/PcmHelper.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs b/Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/ResamplerHelper.cs rename to Ryujinx.Audio/Renderer/Dsp/ResamplerHelper.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/State/AdpcmLoopContext.cs b/Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/State/AdpcmLoopContext.cs rename to Ryujinx.Audio/Renderer/Dsp/State/AdpcmLoopContext.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/State/AuxiliaryBufferHeader.cs b/Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/State/AuxiliaryBufferHeader.cs rename to Ryujinx.Audio/Renderer/Dsp/State/AuxiliaryBufferHeader.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/State/BiquadFilterState.cs b/Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/State/BiquadFilterState.cs rename to Ryujinx.Audio/Renderer/Dsp/State/BiquadFilterState.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/State/DelayState.cs b/Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/State/DelayState.cs rename to Ryujinx.Audio/Renderer/Dsp/State/DelayState.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/State/Reverb3dState.cs b/Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/State/Reverb3dState.cs rename to Ryujinx.Audio/Renderer/Dsp/State/Reverb3dState.cs diff --git a/Ryujinx.Audio.Renderer/Dsp/State/ReverbState.cs b/Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Dsp/State/ReverbState.cs rename to Ryujinx.Audio/Renderer/Dsp/State/ReverbState.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/AudioRendererConfiguration.cs b/Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/AudioRendererConfiguration.cs rename to Ryujinx.Audio/Renderer/Parameter/AudioRendererConfiguration.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/BehaviourErrorInfoOutStatus.cs rename to Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/BiquadFilterParameter.cs b/Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/BiquadFilterParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/BiquadFilterParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Effect/AuxiliaryBufferParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Effect/BiquadFilterEffectParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/BufferMixerParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Effect/BufferMixerParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Effect/BufferMixerParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/DelayParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Effect/DelayParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Effect/DelayParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/Reverb3dParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Effect/Reverb3dParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Effect/Reverb3dParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/Effect/ReverbParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Effect/ReverbParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Effect/ReverbParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/EffectInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/EffectInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/EffectInParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/EffectOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs similarity index 98% rename from Ryujinx.Audio.Renderer/Parameter/EffectOutStatus.cs rename to Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs index 7ed2c7ba..c6178165 100644 --- a/Ryujinx.Audio.Renderer/Parameter/EffectOutStatus.cs +++ b/Ryujinx.Audio/Renderer/Parameter/EffectOutStatus.cs @@ -44,7 +44,6 @@ namespace Ryujinx.Audio.Renderer.Parameter /// /// Current effect state. /// - // TODO: enum? public EffectState State; /// diff --git a/Ryujinx.Audio.Renderer/Parameter/MemoryPoolInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/MemoryPoolInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/MemoryPoolInParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/MemoryPoolOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/MemoryPoolOutStatus.cs rename to Ryujinx.Audio/Renderer/Parameter/MemoryPoolOutStatus.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs b/Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs rename to Ryujinx.Audio/Renderer/Parameter/MixInParameterDirtyOnlyUpdate.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/MixParameter.cs b/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs similarity index 95% rename from Ryujinx.Audio.Renderer/Parameter/MixParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/MixParameter.cs index 7f45bca6..1d7f27f4 100644 --- a/Ryujinx.Audio.Renderer/Parameter/MixParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/MixParameter.cs @@ -100,7 +100,7 @@ namespace Ryujinx.Audio.Renderer.Parameter /// private uint _reserved3; - [StructLayout(LayoutKind.Sequential, Size = 4 * RendererConstants.MixBufferCountMax * RendererConstants.MixBufferCountMax, Pack = 1)] + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax * Constants.MixBufferCountMax, Pack = 1)] private struct MixVolumeArray { } /// diff --git a/Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceInParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Performance/PerformanceOutStatus.cs rename to Ryujinx.Audio/Renderer/Parameter/Performance/PerformanceOutStatus.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/RendererInfoOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/RendererInfoOutStatus.cs rename to Ryujinx.Audio/Renderer/Parameter/RendererInfoOutStatus.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/Sink/CircularBufferParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs similarity index 98% rename from Ryujinx.Audio.Renderer/Parameter/Sink/CircularBufferParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs index d688529a..ae06481f 100644 --- a/Ryujinx.Audio.Renderer/Parameter/Sink/CircularBufferParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/Sink/CircularBufferParameter.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Common.Memory; using System.Runtime.InteropServices; diff --git a/Ryujinx.Audio.Renderer/Parameter/Sink/DeviceParameter.cs b/Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/Sink/DeviceParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/Sink/DeviceParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/SinkInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/SinkInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/SinkInParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/SinkOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/SinkOutStatus.cs rename to Ryujinx.Audio/Renderer/Parameter/SinkOutStatus.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/SplitterDestinationInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Parameter/SplitterDestinationInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs index a1c18d57..77d5ddc2 100644 --- a/Ryujinx.Audio.Renderer/Parameter/SplitterDestinationInParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/SplitterDestinationInParameter.cs @@ -58,7 +58,7 @@ namespace Ryujinx.Audio.Renderer.Parameter /// private unsafe fixed byte _reserved[3]; - [StructLayout(LayoutKind.Sequential, Size = 4 * RendererConstants.MixBufferCountMax, Pack = 1)] + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] private struct MixArray { } /// diff --git a/Ryujinx.Audio.Renderer/Parameter/SplitterInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/SplitterInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/SplitterInParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/SplitterInParameterHeader.cs b/Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/SplitterInParameterHeader.cs rename to Ryujinx.Audio/Renderer/Parameter/SplitterInParameterHeader.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/VoiceChannelResourceInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/VoiceChannelResourceInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/VoiceChannelResourceInParameter.cs diff --git a/Ryujinx.Audio.Renderer/Parameter/VoiceInParameter.cs b/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs similarity index 99% rename from Ryujinx.Audio.Renderer/Parameter/VoiceInParameter.cs rename to Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs index 9a18a95a..bced1538 100644 --- a/Ryujinx.Audio.Renderer/Parameter/VoiceInParameter.cs +++ b/Ryujinx.Audio/Renderer/Parameter/VoiceInParameter.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Dsp; using Ryujinx.Common.Memory; diff --git a/Ryujinx.Audio.Renderer/Parameter/VoiceOutStatus.cs b/Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Parameter/VoiceOutStatus.cs rename to Ryujinx.Audio/Renderer/Parameter/VoiceOutStatus.cs diff --git a/Ryujinx.Audio.Renderer/Server/AudioRenderSystem.cs b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs similarity index 94% rename from Ryujinx.Audio.Renderer/Server/AudioRenderSystem.cs rename to Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs index 9b87957a..787b8f9f 100644 --- a/Ryujinx.Audio.Renderer/Server/AudioRenderSystem.cs +++ b/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -15,9 +15,9 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Dsp.Command; -using Ryujinx.Audio.Renderer.Integration; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Server.Effect; using Ryujinx.Audio.Renderer.Server.MemoryPool; @@ -134,10 +134,10 @@ namespace Ryujinx.Audio.Renderer.Server _sampleRate = parameter.SampleRate; _sampleCount = parameter.SampleCount; _mixBufferCount = parameter.MixBufferCount; - _voiceChannelCountMax = RendererConstants.VoiceChannelCountMax; + _voiceChannelCountMax = Constants.VoiceChannelCountMax; _upsamplerCount = parameter.SinkCount + parameter.SubMixBufferCount; _appletResourceId = appletResourceId; - _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * RendererConstants.VoiceWaveBufferCount; + _memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; _executionMode = parameter.ExecutionMode; _sessionId = sessionId; MemoryManager = memoryManager; @@ -160,14 +160,14 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.WorkBufferTooSmall; } - Memory upSamplerWorkBuffer = workBufferAllocator.Allocate(RendererConstants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10); + Memory upSamplerWorkBuffer = workBufferAllocator.Allocate(Constants.TargetSampleCount * (_voiceChannelCountMax + _mixBufferCount) * _upsamplerCount, 0x10); if (upSamplerWorkBuffer.IsEmpty) { return ResultCode.WorkBufferTooSmall; } - _depopBuffer = workBufferAllocator.Allocate((ulong)BitUtils.AlignUp(parameter.MixBufferCount, RendererConstants.BufferAlignment), RendererConstants.BufferAlignment); + _depopBuffer = workBufferAllocator.Allocate((ulong)BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment); if (_depopBuffer.IsEmpty) { @@ -177,7 +177,7 @@ namespace Ryujinx.Audio.Renderer.Server // Invalidate DSP cache on what was currently allocated with workBuffer. AudioProcessorMemoryManager.InvalidateDspCache(_dspMemoryPoolState.Translate(workBuffer, workBufferAllocator.Offset), workBufferAllocator.Offset); - Debug.Assert((workBufferAllocator.Offset % RendererConstants.BufferAlignment) == 0); + Debug.Assert((workBufferAllocator.Offset % Constants.BufferAlignment) == 0); Memory voices = workBufferAllocator.Allocate(parameter.VoiceCount, VoiceState.Alignment); @@ -253,7 +253,7 @@ namespace Ryujinx.Audio.Renderer.Server } // Initialize the final mix id - mixes.Span[0].MixId = RendererConstants.FinalMixId; + mixes.Span[0].MixId = Constants.FinalMixId; Memory sortedMixesState = workBufferAllocator.Allocate(mixesCount, 0x10); @@ -318,7 +318,7 @@ namespace Ryujinx.Audio.Renderer.Server { ulong performanceBufferSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref _behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; - _performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, RendererConstants.BufferAlignment); + _performanceBuffer = workBufferAllocator.Allocate(performanceBufferSize, Constants.BufferAlignment); if (_performanceBuffer.IsEmpty) { @@ -504,7 +504,7 @@ namespace Ryujinx.Audio.Renderer.Server { double ticks = ARMeilleure.State.ExecutionContext.ElapsedTicks * ARMeilleure.State.ExecutionContext.TickFrequency; - return (ulong)(ticks * RendererConstants.TargetTimerFrequency); + return (ulong)(ticks * Constants.TargetTimerFrequency); } private uint ComputeVoiceDrop(CommandBuffer commandBuffer, long voicesEstimatedTime, long deltaTimeDsp) @@ -544,7 +544,7 @@ namespace Ryujinx.Audio.Renderer.Server ref VoiceState voice = ref _voiceContext.GetState(NodeIdHelper.GetBase(targetNodeId)); - if (voice.Priority == RendererConstants.VoiceHighestPriority) + if (voice.Priority == Constants.VoiceHighestPriority) { break; } @@ -641,7 +641,7 @@ namespace Ryujinx.Audio.Renderer.Server private int GetMaxAllocatedTimeForDsp() { - return (int)(RendererConstants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f)); + return (int)(Constants.AudioProcessorMaxUpdateTimePerSessions * _behaviourContext.GetAudioRendererProcessingTimeLimit() * (GetRenderingTimeLimit() / 100.0f)); } public void SendCommands() @@ -714,7 +714,7 @@ namespace Ryujinx.Audio.Renderer.Server { return new RendererSystemContext { - ChannelCount = _manager.OutputDevices[_sessionId].GetChannelCount(), + ChannelCount = _manager.Processor.OutputDevices[_sessionId].GetChannelCount(), BehaviourContext = _behaviourContext, DepopBuffer = _depopBuffer, MixBufferCount = GetMixBufferCount(), @@ -736,18 +736,18 @@ namespace Ryujinx.Audio.Renderer.Server uint mixesCount = parameter.SubMixBufferCount + 1; - uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * RendererConstants.VoiceWaveBufferCount; + uint memoryPoolCount = parameter.EffectCount + parameter.VoiceCount * Constants.VoiceWaveBufferCount; ulong size = 0; // Mix Buffers - size = WorkBufferAllocator.GetTargetSize(size, parameter.SampleCount * (RendererConstants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10); + size = WorkBufferAllocator.GetTargetSize(size, parameter.SampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount), 0x10); // Upsampler workbuffer - size = WorkBufferAllocator.GetTargetSize(size, RendererConstants.TargetSampleCount * (RendererConstants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10); + size = WorkBufferAllocator.GetTargetSize(size, Constants.TargetSampleCount * (Constants.VoiceChannelCountMax + parameter.MixBufferCount) * (parameter.SinkCount + parameter.SubMixBufferCount), 0x10); // Depop buffer - size = WorkBufferAllocator.GetTargetSize(size, (ulong)BitUtils.AlignUp(parameter.MixBufferCount, RendererConstants.BufferAlignment), RendererConstants.BufferAlignment); + size = WorkBufferAllocator.GetTargetSize(size, (ulong)BitUtils.AlignUp(parameter.MixBufferCount, Constants.BufferAlignment), Constants.BufferAlignment); // Voice size = WorkBufferAllocator.GetTargetSize(size, parameter.VoiceCount, VoiceState.Alignment); @@ -779,10 +779,10 @@ namespace Ryujinx.Audio.Renderer.Server { ulong performanceMetricsPerFramesSize = PerformanceManager.GetRequiredBufferSizeForPerformanceMetricsPerFrame(ref parameter, ref behaviourContext) * (parameter.PerformanceMetricFramesCount + 1) + 0xC; - size += BitUtils.AlignUp(performanceMetricsPerFramesSize, RendererConstants.PerformanceMetricsPerFramesSizeAlignment); + size += BitUtils.AlignUp(performanceMetricsPerFramesSize, Constants.PerformanceMetricsPerFramesSizeAlignment); } - return BitUtils.AlignUp(size, RendererConstants.WorkBufferAlignment); + return BitUtils.AlignUp(size, Constants.WorkBufferAlignment); } public ResultCode QuerySystemEvent(out IWritableEvent systemEvent) diff --git a/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs similarity index 92% rename from Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs rename to Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs index f552935b..ec847948 100644 --- a/Ryujinx.Audio.Renderer/Server/AudioRendererManager.cs +++ b/Ryujinx.Audio/Renderer/Server/AudioRendererManager.cs @@ -15,8 +15,8 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Dsp; -using Ryujinx.Audio.Renderer.Integration; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Common.Logging; using Ryujinx.Memory; @@ -72,10 +72,9 @@ namespace Ryujinx.Audio.Renderer.Server private bool _isRunning; /// - /// The output devices associated to each session. + /// The audio device driver to create audio outputs. /// - // TODO: get ride of this with the audout rewrite. - public HardwareDevice[] OutputDevices { get; private set; } + private IHardwareDeviceDriver _deviceDriver; /// /// The instance associated to this manager. @@ -88,8 +87,8 @@ namespace Ryujinx.Audio.Renderer.Server public AudioRendererManager() { Processor = new AudioProcessor(); - _sessionIds = new int[RendererConstants.AudioRendererSessionCountMax]; - _sessions = new AudioRenderSystem[RendererConstants.AudioRendererSessionCountMax]; + _sessionIds = new int[Constants.AudioRendererSessionCountMax]; + _sessions = new AudioRenderSystem[Constants.AudioRendererSessionCountMax]; _activeSessionCount = 0; for (int i = 0; i < _sessionIds.Length; i++) @@ -102,11 +101,11 @@ namespace Ryujinx.Audio.Renderer.Server /// Initialize the . /// /// The events associated to each session. - /// The output devices associated to each session. - public void Initialize(IWritableEvent[] sessionSystemEvents, HardwareDevice[] devices) + /// The device driver to use to create audio outputs. + public void Initialize(IWritableEvent[] sessionSystemEvents, IHardwareDeviceDriver deviceDriver) { _sessionsSystemEvent = sessionSystemEvents; - OutputDevices = devices; + _deviceDriver = deviceDriver; } /// @@ -186,8 +185,7 @@ namespace Ryujinx.Audio.Renderer.Server _isRunning = true; // TODO: virtual device mapping (IAudioDevice) - Processor.SetOutputDevices(OutputDevices); - Processor.Start(); + Processor.Start(_deviceDriver); _workerThread = new Thread(SendCommands) { @@ -330,11 +328,6 @@ namespace Ryujinx.Audio.Renderer.Server } Processor.Dispose(); - - foreach (HardwareDevice device in OutputDevices) - { - device.Dispose(); - } } } } diff --git a/Ryujinx.Audio.Renderer/Server/BehaviourContext.cs b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs similarity index 97% rename from Ryujinx.Audio.Renderer/Server/BehaviourContext.cs rename to Ryujinx.Audio/Renderer/Server/BehaviourContext.cs index 28bcdac5..b31f9e9f 100644 --- a/Ryujinx.Audio.Renderer/Server/BehaviourContext.cs +++ b/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -132,7 +132,7 @@ namespace Ryujinx.Audio.Renderer.Server public BehaviourContext() { UserRevision = 0; - _errorInfos = new ErrorInfo[RendererConstants.MaxErrorInfos]; + _errorInfos = new ErrorInfo[Constants.MaxErrorInfos]; _errorIndex = 0; } @@ -357,7 +357,7 @@ namespace Ryujinx.Audio.Renderer.Server { Debug.Assert(errorInfo.ErrorCode == ResultCode.Success); - if (_errorIndex <= RendererConstants.MaxErrorInfos - 1) + if (_errorIndex <= Constants.MaxErrorInfos - 1) { _errorInfos[_errorIndex++] = errorInfo; } @@ -370,14 +370,14 @@ namespace Ryujinx.Audio.Renderer.Server /// The output error count containing the count of copied. public void CopyErrorInfo(Span errorInfos, out uint errorCount) { - if (errorInfos.Length != RendererConstants.MaxErrorInfos) + if (errorInfos.Length != Constants.MaxErrorInfos) { throw new ArgumentException("Invalid size of errorInfos span!"); } - errorCount = Math.Min(_errorIndex, RendererConstants.MaxErrorInfos); + errorCount = Math.Min(_errorIndex, Constants.MaxErrorInfos); - for (int i = 0; i < RendererConstants.MaxErrorInfos; i++) + for (int i = 0; i < Constants.MaxErrorInfos; i++) { if (i < errorCount) { diff --git a/Ryujinx.Audio.Renderer/Server/CommandBuffer.cs b/Ryujinx.Audio/Renderer/Server/CommandBuffer.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/CommandBuffer.cs rename to Ryujinx.Audio/Renderer/Server/CommandBuffer.cs diff --git a/Ryujinx.Audio.Renderer/Server/CommandGenerator.cs b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs similarity index 95% rename from Ryujinx.Audio.Renderer/Server/CommandGenerator.cs rename to Ryujinx.Audio/Renderer/Server/CommandGenerator.cs index 36c438fe..d2499c1d 100644 --- a/Ryujinx.Audio.Renderer/Server/CommandGenerator.cs +++ b/Ryujinx.Audio/Renderer/Server/CommandGenerator.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Dsp.Command; using Ryujinx.Audio.Renderer.Dsp.State; @@ -53,12 +54,12 @@ namespace Ryujinx.Audio.Renderer.Server _splitterContext = splitterContext; _performanceManager = performanceManager; - _commandBuffer.GenerateClearMixBuffer(RendererConstants.InvalidNodeId); + _commandBuffer.GenerateClearMixBuffer(Constants.InvalidNodeId); } private void GenerateDataSource(ref VoiceState voiceState, Memory dspState, int channelIndex) { - if (voiceState.MixId != RendererConstants.UnusedMixId) + if (voiceState.MixId != Constants.UnusedMixId) { ref MixState mix = ref _mixContext.GetState(voiceState.MixId); @@ -69,7 +70,7 @@ namespace Ryujinx.Audio.Renderer.Server voiceState.NodeId, voiceState.WasPlaying); } - else if (voiceState.SplitterId != RendererConstants.UnusedSplitterId) + else if (voiceState.SplitterId != Constants.UnusedSplitterId) { int destinationId = 0; @@ -88,7 +89,7 @@ namespace Ryujinx.Audio.Renderer.Server { int mixId = destination.DestinationId; - if (mixId < _mixContext.GetCount() && mixId != RendererConstants.UnusedSplitterIdInt) + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) { ref MixState mix = ref _mixContext.GetState(mixId); @@ -156,7 +157,7 @@ namespace Ryujinx.Audio.Renderer.Server if (filter.Enable) { - Memory biquadStateRawMemory = SpanMemoryManager.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * RendererConstants.VoiceBiquadFilterCount); + Memory biquadStateRawMemory = SpanMemoryManager.Cast(state).Slice(VoiceUpdateState.BiquadStateOffset, VoiceUpdateState.BiquadStateSize * Constants.VoiceBiquadFilterCount); Memory stateMemory = SpanMemoryManager.Cast(biquadStateRawMemory); @@ -173,7 +174,7 @@ namespace Ryujinx.Audio.Renderer.Server private void GenerateVoiceMix(Span mixVolumes, Span previousMixVolumes, Memory state, uint bufferOffset, uint bufferCount, uint bufferIndex, int nodeId) { - if (bufferCount > RendererConstants.VoiceChannelCountMax) + if (bufferCount > Constants.VoiceChannelCountMax) { _commandBuffer.GenerateMixRampGrouped(bufferCount, bufferIndex, @@ -287,9 +288,9 @@ namespace Ryujinx.Audio.Renderer.Server voiceState.PreviousVolume = voiceState.Volume; - if (voiceState.MixId == RendererConstants.UnusedMixId) + if (voiceState.MixId == Constants.UnusedMixId) { - if (voiceState.SplitterId != RendererConstants.UnusedSplitterId) + if (voiceState.SplitterId != Constants.UnusedSplitterId) { int destinationId = channelIndex; @@ -310,7 +311,7 @@ namespace Ryujinx.Audio.Renderer.Server { int mixId = destination.DestinationId; - if (mixId < _mixContext.GetCount() && mixId != RendererConstants.UnusedSplitterIdInt) + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) { ref MixState mix = ref _mixContext.GetState(mixId); @@ -541,7 +542,7 @@ namespace Ryujinx.Audio.Renderer.Server { int nodeId = mix.NodeId; - bool isFinalMix = mix.MixId == RendererConstants.FinalMixId; + bool isFinalMix = mix.MixId == Constants.FinalMixId; PerformanceEntryAddresses performanceEntry = new PerformanceEntryAddresses(); @@ -597,7 +598,7 @@ namespace Ryujinx.Audio.Renderer.Server { int effectOrder = effectProcessingOrderArray[i]; - if (effectOrder == RendererConstants.InvalidProcessingOrder) + if (effectOrder == Constants.InvalidProcessingOrder) { break; } @@ -619,11 +620,11 @@ namespace Ryujinx.Audio.Renderer.Server { if (mix.HasAnyDestination()) { - Debug.Assert(mix.DestinationMixId != RendererConstants.UnusedMixId || mix.DestinationSplitterId != RendererConstants.UnusedSplitterId); + Debug.Assert(mix.DestinationMixId != Constants.UnusedMixId || mix.DestinationSplitterId != Constants.UnusedSplitterId); - if (mix.DestinationMixId == RendererConstants.UnusedMixId) + if (mix.DestinationMixId == Constants.UnusedMixId) { - if (mix.DestinationSplitterId != RendererConstants.UnusedSplitterId) + if (mix.DestinationSplitterId != Constants.UnusedSplitterId) { int destinationId = 0; @@ -644,7 +645,7 @@ namespace Ryujinx.Audio.Renderer.Server { int mixId = destination.DestinationId; - if (mixId < _mixContext.GetCount() && mixId != RendererConstants.UnusedSplitterIdInt) + if (mixId < _mixContext.GetCount() && mixId != Constants.UnusedSplitterIdInt) { ref MixState destinationMix = ref _mixContext.GetState(mixId); @@ -727,7 +728,7 @@ namespace Ryujinx.Audio.Renderer.Server { ref MixState sortedState = ref _mixContext.GetSortedState(id); - if (sortedState.IsUsed && sortedState.MixId != RendererConstants.FinalMixId) + if (sortedState.IsUsed && sortedState.MixId != Constants.FinalMixId) { int nodeId = sortedState.NodeId; @@ -833,7 +834,7 @@ namespace Ryujinx.Audio.Renderer.Server private void GenerateCircularBuffer(CircularBufferSink sink, ref MixState finalMix) { - _commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, RendererConstants.InvalidNodeId); + _commandBuffer.GenerateCircularBuffer(finalMix.BufferOffset, sink, Constants.InvalidNodeId); } private void GenerateDevice(DeviceSink sink, ref MixState finalMix) @@ -851,17 +852,16 @@ namespace Ryujinx.Audio.Renderer.Server sink.Parameter.Input.ToSpan(), sink.Parameter.Input.ToSpan(), sink.DownMixCoefficients, - RendererConstants.InvalidNodeId); + Constants.InvalidNodeId); } - // NOTE: we do the downmixing at the DSP level as right now the renderer interface doesn't use audout. - // TODO: Remove this when audout is rewritten. + // NOTE: We do the downmixing at the DSP level as it's easier that way. else if (_rendererContext.ChannelCount == 2 && sink.Parameter.InputCount == 6) { _commandBuffer.GenerateDownMixSurroundToStereo(finalMix.BufferOffset, sink.Parameter.Input.ToSpan(), sink.Parameter.Input.ToSpan(), - RendererConstants.DefaultSurroundToStereoCoefficients, - RendererConstants.InvalidNodeId); + Constants.DefaultSurroundToStereoCoefficients, + Constants.InvalidNodeId); } CommandList commandList = _commandBuffer.CommandList; @@ -875,14 +875,14 @@ namespace Ryujinx.Audio.Renderer.Server commandList.BufferCount, commandList.SampleCount, commandList.SampleRate, - RendererConstants.InvalidNodeId); + Constants.InvalidNodeId); } _commandBuffer.GenerateDeviceSink(finalMix.BufferOffset, sink, _rendererContext.SessionId, commandList.Buffers, - RendererConstants.InvalidNodeId); + Constants.InvalidNodeId); } private void GenerateSink(BaseSink sink, ref MixState finalMix) diff --git a/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs rename to Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion1.cs diff --git a/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs rename to Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion2.cs diff --git a/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs similarity index 99% rename from Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs rename to Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs index 8e15d642..b48ff8b5 100644 --- a/Ryujinx.Audio.Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs +++ b/Ryujinx.Audio/Renderer/Server/CommandProcessingTimeEstimatorVersion3.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Dsp.Command; using System; diff --git a/Ryujinx.Audio.Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Effect/AuxiliaryBufferEffect.cs rename to Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs diff --git a/Ryujinx.Audio.Renderer/Server/Effect/BaseEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs similarity index 99% rename from Ryujinx.Audio.Renderer/Server/Effect/BaseEffect.cs rename to Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs index 7c491fd1..7529f894 100644 --- a/Ryujinx.Audio.Renderer/Server/Effect/BaseEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs @@ -77,7 +77,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect IsEnabled = false; BufferUnmapped = false; - MixId = RendererConstants.UnusedMixId; + MixId = Constants.UnusedMixId; ProcessingOrder = uint.MaxValue; WorkBuffers = new AddressInfo[2]; diff --git a/Ryujinx.Audio.Renderer/Server/Effect/BiquadFilterEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Server/Effect/BiquadFilterEffect.cs rename to Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs index aadf2844..35ba8a0d 100644 --- a/Ryujinx.Audio.Renderer/Server/Effect/BiquadFilterEffect.cs +++ b/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs @@ -47,7 +47,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public BiquadFilterEffect() { Parameter = new BiquadFilterEffectParameter(); - State = new BiquadFilterState[RendererConstants.ChannelCountMax]; + State = new BiquadFilterState[Constants.ChannelCountMax]; } public override EffectType TargetEffectType => EffectType.BiquadFilter; diff --git a/Ryujinx.Audio.Renderer/Server/Effect/BufferMixEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Effect/BufferMixEffect.cs rename to Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs diff --git a/Ryujinx.Audio.Renderer/Server/Effect/DelayEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Effect/DelayEffect.cs rename to Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs diff --git a/Ryujinx.Audio.Renderer/Server/Effect/EffectContext.cs b/Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Effect/EffectContext.cs rename to Ryujinx.Audio/Renderer/Server/Effect/EffectContext.cs diff --git a/Ryujinx.Audio.Renderer/Server/Effect/Reverb3dEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Effect/Reverb3dEffect.cs rename to Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs diff --git a/Ryujinx.Audio.Renderer/Server/Effect/ReverbEffect.cs b/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Effect/ReverbEffect.cs rename to Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs diff --git a/Ryujinx.Audio.Renderer/Server/Effect/UsageState.cs b/Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Effect/UsageState.cs rename to Ryujinx.Audio/Renderer/Server/Effect/UsageState.cs diff --git a/Ryujinx.Audio.Renderer/Server/ICommandProcessingTimeEstimator.cs b/Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/ICommandProcessingTimeEstimator.cs rename to Ryujinx.Audio/Renderer/Server/ICommandProcessingTimeEstimator.cs diff --git a/Ryujinx.Audio.Renderer/Server/MemoryPool/AddressInfo.cs b/Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/MemoryPool/AddressInfo.cs rename to Ryujinx.Audio/Renderer/Server/MemoryPool/AddressInfo.cs diff --git a/Ryujinx.Audio.Renderer/Server/MemoryPool/MemoryPoolState.cs b/Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/MemoryPool/MemoryPoolState.cs rename to Ryujinx.Audio/Renderer/Server/MemoryPool/MemoryPoolState.cs diff --git a/Ryujinx.Audio.Renderer/Server/MemoryPool/PoolMapper.cs b/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/MemoryPool/PoolMapper.cs rename to Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs diff --git a/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs b/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs similarity index 97% rename from Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs rename to Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs index 5a3c3a63..0e094665 100644 --- a/Ryujinx.Audio.Renderer/Server/Mix/MixContext.cs +++ b/Ryujinx.Audio/Renderer/Server/Mix/MixContext.cs @@ -104,7 +104,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix /// A reference to the final . public ref MixState GetFinalState() { - return ref GetState(RendererConstants.FinalMixId); + return ref GetState(Constants.FinalMixId); } /// @@ -158,13 +158,13 @@ namespace Ryujinx.Audio.Renderer.Server.Mix { uint distance; - if (mix.MixId != RendererConstants.FinalMixId) + if (mix.MixId != Constants.FinalMixId) { int mixId = mix.MixId; for (distance = 0; distance < GetCount(); distance++) { - if (mixId == RendererConstants.UnusedMixId) + if (mixId == Constants.UnusedMixId) { distance = MixState.InvalidDistanceFromFinalMix; break; @@ -180,7 +180,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix mixId = distanceMix.DestinationMixId; - if (mixId == RendererConstants.FinalMixId) + if (mixId == Constants.FinalMixId) { break; } diff --git a/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs b/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs similarity index 99% rename from Ryujinx.Audio.Renderer/Server/Mix/MixState.cs rename to Ryujinx.Audio/Renderer/Server/Mix/MixState.cs index d697488a..028d0885 100644 --- a/Ryujinx.Audio.Renderer/Server/Mix/MixState.cs +++ b/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs @@ -25,7 +25,7 @@ using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; -using static Ryujinx.Audio.Renderer.RendererConstants; +using static Ryujinx.Audio.Constants; namespace Ryujinx.Audio.Renderer.Server.Mix { diff --git a/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceDetailEntry.cs b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/IPerformanceDetailEntry.cs rename to Ryujinx.Audio/Renderer/Server/Performance/IPerformanceDetailEntry.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceEntry.cs b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/IPerformanceEntry.cs rename to Ryujinx.Audio/Renderer/Server/Performance/IPerformanceEntry.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/IPerformanceHeader.cs b/Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/IPerformanceHeader.cs rename to Ryujinx.Audio/Renderer/Server/Performance/IPerformanceHeader.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion1.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion1.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion1.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion2.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceDetailVersion2.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceDetailVersion2.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryAddresses.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryAddresses.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryAddresses.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion1.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion1.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion1.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion2.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceEntryVersion2.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceEntryVersion2.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion1.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceFrameHeaderVersion2.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceManager.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceManager.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceManager.cs diff --git a/Ryujinx.Audio.Renderer/Server/Performance/PerformanceManagerGeneric.cs b/Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Performance/PerformanceManagerGeneric.cs rename to Ryujinx.Audio/Renderer/Server/Performance/PerformanceManagerGeneric.cs diff --git a/Ryujinx.Audio.Renderer/Server/RendererSystemContext.cs b/Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/RendererSystemContext.cs rename to Ryujinx.Audio/Renderer/Server/RendererSystemContext.cs diff --git a/Ryujinx.Audio.Renderer/Server/Sink/BaseSink.cs b/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Sink/BaseSink.cs rename to Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs diff --git a/Ryujinx.Audio.Renderer/Server/Sink/CircularBufferSink.cs b/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs similarity index 97% rename from Ryujinx.Audio.Renderer/Server/Sink/CircularBufferSink.cs rename to Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs index 16d25a65..ceb955e5 100644 --- a/Ryujinx.Audio.Renderer/Server/Sink/CircularBufferSink.cs +++ b/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs @@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink if (IsUsed) { - uint frameSize = RendererConstants.TargetSampleSize * Parameter.SampleCount * Parameter.InputCount; + uint frameSize = Constants.TargetSampleSize * Parameter.SampleCount * Parameter.InputCount; _lastWrittenOffset = _oldWrittenOffset; diff --git a/Ryujinx.Audio.Renderer/Server/Sink/DeviceSink.cs b/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Sink/DeviceSink.cs rename to Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs diff --git a/Ryujinx.Audio.Renderer/Server/Sink/SinkContext.cs b/Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Sink/SinkContext.cs rename to Ryujinx.Audio/Renderer/Server/Sink/SinkContext.cs diff --git a/Ryujinx.Audio.Renderer/Server/Splitter/SplitterContext.cs b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Splitter/SplitterContext.cs rename to Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs diff --git a/Ryujinx.Audio.Renderer/Server/Splitter/SplitterDestination.cs b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs similarity index 95% rename from Ryujinx.Audio.Renderer/Server/Splitter/SplitterDestination.cs rename to Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs index 6d273e45..077bf8ae 100644 --- a/Ryujinx.Audio.Renderer/Server/Splitter/SplitterDestination.cs +++ b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterDestination.cs @@ -64,7 +64,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter [MarshalAs(UnmanagedType.I1)] public bool NeedToUpdateInternalState; - [StructLayout(LayoutKind.Sequential, Size = 4 * RendererConstants.MixBufferCountMax, Pack = 1)] + [StructLayout(LayoutKind.Sequential, Size = 4 * Constants.MixBufferCountMax, Pack = 1)] private struct MixArray { } /// @@ -100,7 +100,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter public SplitterDestination(int id) : this() { Id = id; - DestinationId = RendererConstants.UnusedMixId; + DestinationId = Constants.UnusedMixId; ClearVolumes(); } @@ -157,7 +157,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// True if the is used and has a destination. public bool IsConfigured() { - return IsUsed && DestinationId != RendererConstants.UnusedMixId; + return IsUsed && DestinationId != Constants.UnusedMixId; } /// @@ -167,7 +167,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// The volume for the given destination. public float GetMixVolume(int destinationIndex) { - Debug.Assert(destinationIndex >= 0 && destinationIndex < RendererConstants.MixBufferCountMax); + Debug.Assert(destinationIndex >= 0 && destinationIndex < Constants.MixBufferCountMax); return MixBufferVolume[destinationIndex]; } diff --git a/Ryujinx.Audio.Renderer/Server/Splitter/SplitterState.cs b/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Splitter/SplitterState.cs rename to Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs diff --git a/Ryujinx.Audio.Renderer/Server/StateUpdater.cs b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs similarity index 98% rename from Ryujinx.Audio.Renderer/Server/StateUpdater.cs rename to Ryujinx.Audio/Renderer/Server/StateUpdater.cs index b26e2721..77935b75 100644 --- a/Ryujinx.Audio.Renderer/Server/StateUpdater.cs +++ b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -171,7 +171,7 @@ namespace Ryujinx.Audio.Renderer.Server { VoiceInParameter parameter = parameters[i]; - Memory[] voiceUpdateStates = new Memory[RendererConstants.VoiceChannelCountMax]; + Memory[] voiceUpdateStates = new Memory[Constants.VoiceChannelCountMax]; ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; @@ -327,9 +327,9 @@ namespace Ryujinx.Audio.Renderer.Server { if (parameters[i].IsUsed) { - if (parameters[i].DestinationMixId != RendererConstants.UnusedMixId && + if (parameters[i].DestinationMixId != Constants.UnusedMixId && parameters[i].DestinationMixId > maxMixStateCount && - parameters[i].MixId != RendererConstants.FinalMixId) + parameters[i].MixId != Constants.FinalMixId) { return true; } diff --git a/Ryujinx.Audio.Renderer/Server/Types/AudioRendererExecutionMode.cs b/Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Types/AudioRendererExecutionMode.cs rename to Ryujinx.Audio/Renderer/Server/Types/AudioRendererExecutionMode.cs diff --git a/Ryujinx.Audio.Renderer/Server/Types/AudioRendererRenderingDevice.cs b/Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Types/AudioRendererRenderingDevice.cs rename to Ryujinx.Audio/Renderer/Server/Types/AudioRendererRenderingDevice.cs diff --git a/Ryujinx.Audio.Renderer/Server/Types/PlayState.cs b/Ryujinx.Audio/Renderer/Server/Types/PlayState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Types/PlayState.cs rename to Ryujinx.Audio/Renderer/Server/Types/PlayState.cs diff --git a/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerManager.cs b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs similarity index 93% rename from Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerManager.cs rename to Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs index fd396d9f..6878123b 100644 --- a/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerManager.cs +++ b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerManager.cs @@ -72,12 +72,12 @@ namespace Ryujinx.Audio.Renderer.Server.Upsampler { if (_upsamplers[i] == null) { - _upsamplers[i] = new UpsamplerState(this, i, _upSamplerWorkBuffer.Slice(workBufferOffset, RendererConstants.UpSampleEntrySize), RendererConstants.TargetSampleCount); + _upsamplers[i] = new UpsamplerState(this, i, _upSamplerWorkBuffer.Slice(workBufferOffset, Constants.UpSampleEntrySize), Constants.TargetSampleCount); return _upsamplers[i]; } - workBufferOffset += RendererConstants.UpSampleEntrySize; + workBufferOffset += Constants.UpSampleEntrySize; } } diff --git a/Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerState.cs b/Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Upsampler/UpsamplerState.cs rename to Ryujinx.Audio/Renderer/Server/Upsampler/UpsamplerState.cs diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs b/Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Voice/VoiceChannelResource.cs rename to Ryujinx.Audio/Renderer/Server/Voice/VoiceChannelResource.cs diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs b/Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Voice/VoiceContext.cs rename to Ryujinx.Audio/Renderer/Server/Voice/VoiceContext.cs diff --git a/Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs b/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs similarity index 96% rename from Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs rename to Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs index f30ee2f7..89abe9aa 100644 --- a/Ryujinx.Audio.Renderer/Server/Voice/VoiceState.cs +++ b/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs @@ -15,6 +15,7 @@ // along with this program. If not, see . // +using Ryujinx.Audio.Common; using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Server.MemoryPool; @@ -190,7 +191,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// This was added on REV5. public byte FlushWaveBufferCount; - [StructLayout(LayoutKind.Sequential, Size = RendererConstants.VoiceBiquadFilterCount)] + [StructLayout(LayoutKind.Sequential, Size = Constants.VoiceBiquadFilterCount)] private struct BiquadFilterNeedInitializationArrayStruct { } /// @@ -209,7 +210,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice BufferInfoUnmapped = false; FlushWaveBufferCount = 0; PlayState = Types.PlayState.Stopped; - Priority = RendererConstants.VoiceLowestPriority; + Priority = Constants.VoiceLowestPriority; Id = 0; NodeId = 0; SampleRate = 0; @@ -221,8 +222,8 @@ namespace Ryujinx.Audio.Renderer.Server.Voice BiquadFilters.ToSpan().Fill(new BiquadFilterParameter()); WaveBuffersCount = 0; WaveBuffersIndex = 0; - MixId = RendererConstants.UnusedMixId; - SplitterId = RendererConstants.UnusedSplitterId; + MixId = Constants.UnusedMixId; + SplitterId = Constants.UnusedSplitterId; DataSourceStateAddressInfo.Setup(0, 0); InitializeWaveBuffers(); @@ -260,7 +261,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// True if the mix has any destinations. public bool HasAnyDestination() { - return MixId != RendererConstants.UnusedMixId || SplitterId != RendererConstants.UnusedSplitterId; + return MixId != Constants.UnusedMixId || SplitterId != Constants.UnusedSplitterId; } /// @@ -321,7 +322,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice } else { - SplitterId = RendererConstants.UnusedSplitterId; + SplitterId = Constants.UnusedSplitterId; } parameter.ChannelResourceIds.ToSpan().CopyTo(ChannelResourceIds.ToSpan()); @@ -442,7 +443,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The behaviour context. public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, Memory[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) { - errorInfos = new ErrorInfo[RendererConstants.VoiceWaveBufferCount * 2]; + errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2]; if (parameter.IsNew) { @@ -456,7 +457,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[0].Span[0]; - for (int i = 0; i < RendererConstants.VoiceWaveBufferCount; i++) + for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) { UpdateWaveBuffer(errorInfos.AsSpan().Slice(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); } @@ -570,12 +571,12 @@ namespace Ryujinx.Audio.Renderer.Server.Voice { ref VoiceUpdateState voiceUpdateState = ref voiceUpdateStates[j].Span[0]; - voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; + voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount; voiceUpdateState.WaveBufferConsumed++; voiceUpdateState.IsWaveBufferValid[(int)waveBufferIndex] = false; } - waveBufferIndex = (waveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; + waveBufferIndex = (waveBufferIndex + 1) % Constants.VoiceWaveBufferCount; } } @@ -640,7 +641,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice if (voiceUpdateState.IsWaveBufferValid[i]) { - voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % RendererConstants.VoiceWaveBufferCount; + voiceUpdateState.WaveBufferIndex = (voiceUpdateState.WaveBufferIndex + 1) % Constants.VoiceWaveBufferCount; voiceUpdateState.WaveBufferConsumed++; } @@ -702,7 +703,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice IsNew = false; } - Memory[] voiceUpdateStates = new Memory[RendererConstants.VoiceChannelCountMax]; + Memory[] voiceUpdateStates = new Memory[Constants.VoiceChannelCountMax]; for (int i = 0; i < ChannelsCount; i++) { diff --git a/Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs b/Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Server/Voice/WaveBuffer.cs rename to Ryujinx.Audio/Renderer/Server/Voice/WaveBuffer.cs diff --git a/Ryujinx.Audio.Renderer/Utils/AudioProcessorMemoryManager.cs b/Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Utils/AudioProcessorMemoryManager.cs rename to Ryujinx.Audio/Renderer/Utils/AudioProcessorMemoryManager.cs diff --git a/Ryujinx.Audio.Renderer/Utils/BitArray.cs b/Ryujinx.Audio/Renderer/Utils/BitArray.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Utils/BitArray.cs rename to Ryujinx.Audio/Renderer/Utils/BitArray.cs diff --git a/Ryujinx.Audio.Renderer/Utils/FileHardwareDevice.cs b/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs similarity index 94% rename from Ryujinx.Audio.Renderer/Utils/FileHardwareDevice.cs rename to Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs index d5cbb8df..2008bafc 100644 --- a/Ryujinx.Audio.Renderer/Utils/FileHardwareDevice.cs +++ b/Ryujinx.Audio/Renderer/Utils/FileHardwareDevice.cs @@ -15,7 +15,7 @@ // along with this program. If not, see . // -using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Integration; using System; using System.IO; using System.Runtime.InteropServices; @@ -24,9 +24,9 @@ using System.Text; namespace Ryujinx.Audio.Renderer.Utils { /// - /// A that outputs to a wav file. + /// A that outputs to a wav file. /// - public class FileHardwareDevice : HardwareDevice + public class FileHardwareDevice : IHardwareDevice { private FileStream _stream; private uint _channelCount; diff --git a/Ryujinx.Audio.Renderer/Utils/Mailbox.cs b/Ryujinx.Audio/Renderer/Utils/Mailbox.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Utils/Mailbox.cs rename to Ryujinx.Audio/Renderer/Utils/Mailbox.cs diff --git a/Ryujinx.Audio.Renderer/Utils/SpanIOHelper.cs b/Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Utils/SpanIOHelper.cs rename to Ryujinx.Audio/Renderer/Utils/SpanIOHelper.cs diff --git a/Ryujinx.Audio.Renderer/Utils/SpanMemoryManager.cs b/Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs similarity index 100% rename from Ryujinx.Audio.Renderer/Utils/SpanMemoryManager.cs rename to Ryujinx.Audio/Renderer/Utils/SpanMemoryManager.cs diff --git a/Ryujinx.Audio.Renderer/Utils/SplitterHardwareDevice.cs b/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs similarity index 84% rename from Ryujinx.Audio.Renderer/Utils/SplitterHardwareDevice.cs rename to Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs index f23bde8a..c5411ac0 100644 --- a/Ryujinx.Audio.Renderer/Utils/SplitterHardwareDevice.cs +++ b/Ryujinx.Audio/Renderer/Utils/SplitterHardwareDevice.cs @@ -15,17 +15,17 @@ // along with this program. If not, see . // -using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Integration; using System; namespace Ryujinx.Audio.Renderer.Utils { - public class SplitterHardwareDevice : HardwareDevice + public class SplitterHardwareDevice : IHardwareDevice { - private HardwareDevice _baseDevice; - private HardwareDevice _secondaryDevice; + private IHardwareDevice _baseDevice; + private IHardwareDevice _secondaryDevice; - public SplitterHardwareDevice(HardwareDevice baseDevice, HardwareDevice secondaryDevice) + public SplitterHardwareDevice(IHardwareDevice baseDevice, IHardwareDevice secondaryDevice) { _baseDevice = baseDevice; _secondaryDevice = secondaryDevice; diff --git a/Ryujinx.Audio/Renderers/DummyAudioOut.cs b/Ryujinx.Audio/Renderers/DummyAudioOut.cs deleted file mode 100644 index cd197592..00000000 --- a/Ryujinx.Audio/Renderers/DummyAudioOut.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace Ryujinx.Audio -{ - /// - /// A Dummy audio renderer that does not output any audio - /// - public class DummyAudioOut : IAalOutput - { - private int _lastTrackId = 1; - private float _volume = 1.0f; - - private ConcurrentQueue _trackIds; - private ConcurrentQueue _buffers; - private ConcurrentDictionary _releaseCallbacks; - private ulong _playedSampleCount; - - public DummyAudioOut() - { - _buffers = new ConcurrentQueue(); - _trackIds = new ConcurrentQueue(); - _releaseCallbacks = new ConcurrentDictionary(); - } - - /// - /// Dummy audio output is always available, Baka! - /// - public static bool IsSupported => true; - - public PlaybackState GetState(int trackId) => PlaybackState.Stopped; - - public bool SupportsChannelCount(int channels) - { - return true; - } - - public int OpenHardwareTrack(int sampleRate, int hardwareChannels, int virtualChannels, ReleaseCallback callback) - { - if (!_trackIds.TryDequeue(out int trackId)) - { - trackId = ++_lastTrackId; - } - - _releaseCallbacks[trackId] = callback; - - return trackId; - } - - public void CloseTrack(int trackId) - { - _trackIds.Enqueue(trackId); - _releaseCallbacks.Remove(trackId, out _); - } - - public bool ContainsBuffer(int trackID, long bufferTag) => false; - - public long[] GetReleasedBuffers(int trackId, int maxCount) - { - List bufferTags = new List(); - - for (int i = 0; i < maxCount; i++) - { - if (!_buffers.TryDequeue(out long tag)) - { - break; - } - - bufferTags.Add(tag); - } - - return bufferTags.ToArray(); - } - - public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct - { - _buffers.Enqueue(bufferTag); - - _playedSampleCount += (ulong)buffer.Length; - - if (_releaseCallbacks.TryGetValue(trackId, out var callback)) - { - callback?.Invoke(); - } - } - - public void Start(int trackId) { } - - public void Stop(int trackId) { } - - public uint GetBufferCount(int trackId) => (uint)_buffers.Count; - - public ulong GetPlayedSampleCount(int trackId) => _playedSampleCount; - - public bool FlushBuffers(int trackId) => false; - - public float GetVolume(int trackId) => _volume; - - public void SetVolume(int trackId, float volume) - { - _volume = volume; - } - - public void Dispose() - { - _buffers.Clear(); - } - } -} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs deleted file mode 100644 index 1ad82319..00000000 --- a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs +++ /dev/null @@ -1,404 +0,0 @@ -using OpenTK.Audio; -using OpenTK.Audio.OpenAL; -using System; -using System.Collections.Concurrent; -using System.Runtime.InteropServices; -using System.Threading; - -namespace Ryujinx.Audio -{ - /// - /// An audio renderer that uses OpenAL as the audio backend - /// - public class OpenALAudioOut : IAalOutput, IDisposable - { - /// - /// The maximum amount of tracks we can issue simultaneously - /// - private const int MaxTracks = 256; - - /// - /// The audio context - /// - private AudioContext _context; - - /// - /// An object pool containing objects - /// - private ConcurrentDictionary _tracks; - - /// - /// True if the thread need to keep polling - /// - private bool _keepPolling; - - /// - /// The poller thread audio context - /// - private Thread _audioPollerThread; - - /// - /// True if OpenAL is supported on the device - /// - public static bool IsSupported - { - get - { - try - { - return AudioContext.AvailableDevices.Count > 0; - } - catch - { - return false; - } - } - } - - public OpenALAudioOut() - { - _context = new AudioContext(); - _tracks = new ConcurrentDictionary(); - _keepPolling = true; - _audioPollerThread = new Thread(AudioPollerWork) - { - Name = "Audio.PollerThread" - }; - - _audioPollerThread.Start(); - } - - private void AudioPollerWork() - { - do - { - foreach (OpenALAudioTrack track in _tracks.Values) - { - lock (track) - { - track.CallReleaseCallbackIfNeeded(); - } - } - - // If it's not slept it will waste cycles. - Thread.Sleep(10); - } - while (_keepPolling); - - foreach (OpenALAudioTrack track in _tracks.Values) - { - track.Dispose(); - } - - _tracks.Clear(); - _context.Dispose(); - } - - public bool SupportsChannelCount(int channels) - { - // NOTE: OpenAL doesn't give us a way to know if the 5.1 setup is supported by hardware or actually emulated. - // TODO: find a way to determine hardware support. - return channels == 1 || channels == 2; - } - - /// - /// Creates a new audio track with the specified parameters - /// - /// The requested sample rate - /// The requested hardware channels - /// The requested virtual channels - /// A that represents the delegate to invoke when a buffer has been released by the audio track - /// The created track's Track ID - public int OpenHardwareTrack(int sampleRate, int hardwareChannels, int virtualChannels, ReleaseCallback callback) - { - OpenALAudioTrack track = new OpenALAudioTrack(sampleRate, GetALFormat(hardwareChannels), hardwareChannels, virtualChannels, callback); - - for (int id = 0; id < MaxTracks; id++) - { - if (_tracks.TryAdd(id, track)) - { - return id; - } - } - - return -1; - } - - private ALFormat GetALFormat(int channels) - { - switch (channels) - { - case 1: return ALFormat.Mono16; - case 2: return ALFormat.Stereo16; - case 6: return ALFormat.Multi51Chn16Ext; - } - - throw new ArgumentOutOfRangeException(nameof(channels)); - } - - /// - /// Stops playback and closes the track specified by - /// - /// The ID of the track to close - public void CloseTrack(int trackId) - { - if (_tracks.TryRemove(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - track.Dispose(); - } - } - } - - /// - /// Returns a value indicating whether the specified buffer is currently reserved by the specified track - /// - /// The track to check - /// The buffer tag to check - public bool ContainsBuffer(int trackId, long bufferTag) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - return track.ContainsBuffer(bufferTag); - } - } - - return false; - } - - /// - /// Gets a list of buffer tags the specified track is no longer reserving - /// - /// The track to retrieve buffer tags from - /// The maximum amount of buffer tags to retrieve - /// Buffers released by the specified track - public long[] GetReleasedBuffers(int trackId, int maxCount) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - return track.GetReleasedBuffers(maxCount); - } - } - - return null; - } - - /// - /// Appends an audio buffer to the specified track - /// - /// The sample type of the buffer - /// The track to append the buffer to - /// The internal tag of the buffer - /// The buffer to append to the track - public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - int bufferId = track.AppendBuffer(bufferTag); - - // Do we need to downmix? - if (track.HardwareChannels != track.VirtualChannels) - { - short[] downmixedBuffer; - - ReadOnlySpan bufferPCM16 = MemoryMarshal.Cast(buffer); - - if (track.VirtualChannels == 6) - { - downmixedBuffer = Downmixing.DownMixSurroundToStereo(bufferPCM16); - - if (track.HardwareChannels == 1) - { - downmixedBuffer = Downmixing.DownMixStereoToMono(downmixedBuffer); - } - } - else if (track.VirtualChannels == 2) - { - downmixedBuffer = Downmixing.DownMixStereoToMono(bufferPCM16); - } - else - { - throw new NotImplementedException($"Downmixing from {track.VirtualChannels} to {track.HardwareChannels} not implemented!"); - } - - AL.BufferData(bufferId, track.Format, downmixedBuffer, downmixedBuffer.Length * sizeof(ushort), track.SampleRate); - } - else - { - AL.BufferData(bufferId, track.Format, buffer, buffer.Length * sizeof(ushort), track.SampleRate); - } - - AL.SourceQueueBuffer(track.SourceId, bufferId); - - StartPlaybackIfNeeded(track); - - track.PlayedSampleCount += (ulong)buffer.Length; - } - } - } - - /// - /// Starts playback - /// - /// The ID of the track to start playback on - public void Start(int trackId) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - track.State = PlaybackState.Playing; - - StartPlaybackIfNeeded(track); - } - } - } - - private void StartPlaybackIfNeeded(OpenALAudioTrack track) - { - AL.GetSource(track.SourceId, ALGetSourcei.SourceState, out int stateInt); - - ALSourceState State = (ALSourceState)stateInt; - - if (State != ALSourceState.Playing && track.State == PlaybackState.Playing) - { - AL.SourcePlay(track.SourceId); - } - } - - /// - /// Stops playback - /// - /// The ID of the track to stop playback on - public void Stop(int trackId) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - track.State = PlaybackState.Stopped; - - AL.SourceStop(track.SourceId); - } - } - } - - /// - /// Get track buffer count - /// - /// The ID of the track to get buffer count - public uint GetBufferCount(int trackId) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - return track.BufferCount; - } - } - - return 0; - } - - /// - /// Get track played sample count - /// - /// The ID of the track to get played sample count - public ulong GetPlayedSampleCount(int trackId) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - return track.PlayedSampleCount; - } - } - - return 0; - } - - /// - /// Flush all track buffers - /// - /// The ID of the track to flush - public bool FlushBuffers(int trackId) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - track.FlushBuffers(); - } - } - - return false; - } - - /// - /// Set track volume - /// - /// The ID of the track to set volume - /// The volume of the track - public void SetVolume(int trackId, float volume) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - track.SetVolume(volume); - } - } - } - - /// - /// Get track volume - /// - /// The ID of the track to get volume - public float GetVolume(int trackId) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - lock (track) - { - return track.GetVolume(); - } - } - - return 1.0f; - } - - /// - /// Gets the current playback state of the specified track - /// - /// The track to retrieve the playback state for - public PlaybackState GetState(int trackId) - { - if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) - { - return track.State; - } - - return PlaybackState.Stopped; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _keepPolling = false; - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs deleted file mode 100644 index 690129eb..00000000 --- a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs +++ /dev/null @@ -1,183 +0,0 @@ -using OpenTK.Audio.OpenAL; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; - -namespace Ryujinx.Audio -{ - internal class OpenALAudioTrack : IDisposable - { - public int SourceId { get; private set; } - public int SampleRate { get; private set; } - public ALFormat Format { get; private set; } - public PlaybackState State { get; set; } - - public int HardwareChannels { get; } - public int VirtualChannels { get; } - public uint BufferCount => (uint)_buffers.Count; - public ulong PlayedSampleCount { get; set; } - - private ReleaseCallback _callback; - - private ConcurrentDictionary _buffers; - - private Queue _queuedTagsQueue; - private Queue _releasedTagsQueue; - - private bool _disposed; - - public OpenALAudioTrack(int sampleRate, ALFormat format, int hardwareChannels, int virtualChannels, ReleaseCallback callback) - { - SampleRate = sampleRate; - Format = format; - State = PlaybackState.Stopped; - SourceId = AL.GenSource(); - - HardwareChannels = hardwareChannels; - VirtualChannels = virtualChannels; - - _callback = callback; - - _buffers = new ConcurrentDictionary(); - - _queuedTagsQueue = new Queue(); - _releasedTagsQueue = new Queue(); - } - - public bool ContainsBuffer(long tag) - { - foreach (long queuedTag in _queuedTagsQueue) - { - if (queuedTag == tag) - { - return true; - } - } - - return false; - } - - public long[] GetReleasedBuffers(int count) - { - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); - - releasedCount += _releasedTagsQueue.Count; - - if (count > releasedCount) - { - count = releasedCount; - } - - List tags = new List(); - - while (count-- > 0 && _releasedTagsQueue.TryDequeue(out long tag)) - { - tags.Add(tag); - } - - while (count-- > 0 && _queuedTagsQueue.TryDequeue(out long tag)) - { - AL.SourceUnqueueBuffers(SourceId, 1); - - tags.Add(tag); - } - - return tags.ToArray(); - } - - public int AppendBuffer(long tag) - { - if (_disposed) - { - throw new ObjectDisposedException(GetType().Name); - } - - int id = AL.GenBuffer(); - - _buffers.AddOrUpdate(tag, id, (key, oldId) => - { - AL.DeleteBuffer(oldId); - - return id; - }); - - _queuedTagsQueue.Enqueue(tag); - - return id; - } - - public void CallReleaseCallbackIfNeeded() - { - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); - - if (releasedCount > 0) - { - // If we signal, then we also need to have released buffers available - // to return when GetReleasedBuffers is called. - // If playback needs to be re-started due to all buffers being processed, - // then OpenAL zeros the counts (ReleasedCount), so we keep it on the queue. - while (releasedCount-- > 0 && _queuedTagsQueue.TryDequeue(out long tag)) - { - AL.SourceUnqueueBuffers(SourceId, 1); - - _releasedTagsQueue.Enqueue(tag); - } - - _callback(); - } - } - - public bool FlushBuffers() - { - while (_queuedTagsQueue.TryDequeue(out long tag)) - { - _releasedTagsQueue.Enqueue(tag); - } - - _callback(); - - foreach (var buffer in _buffers) - { - AL.DeleteBuffer(buffer.Value); - } - - bool heldBuffers = _buffers.Count > 0; - - _buffers.Clear(); - - return heldBuffers; - } - - public void SetVolume(float volume) - { - AL.Source(SourceId, ALSourcef.Gain, volume); - } - - public float GetVolume() - { - AL.GetSource(SourceId, ALSourcef.Gain, out float volume); - - return volume; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing && !_disposed) - { - _disposed = true; - - AL.DeleteSource(SourceId); - - foreach (int id in _buffers.Values) - { - AL.DeleteBuffer(id); - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs deleted file mode 100644 index 92bf42c4..00000000 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs +++ /dev/null @@ -1,361 +0,0 @@ -using Ryujinx.Audio.SoundIo; -using SoundIOSharp; -using System.Collections.Generic; - -namespace Ryujinx.Audio -{ - /// - /// An audio renderer that uses libsoundio as the audio backend - /// - public class SoundIoAudioOut : IAalOutput - { - /// - /// The maximum amount of tracks we can issue simultaneously - /// - private const int MaximumTracks = 256; - - /// - /// The audio context - /// - private SoundIO _audioContext; - - /// - /// The audio device - /// - private SoundIODevice _audioDevice; - - /// - /// An object pool containing objects - /// - private SoundIoAudioTrackPool _trackPool; - - /// - /// True if SoundIO is supported on the device - /// - public static bool IsSupported - { - get - { - return IsSupportedInternal(); - } - } - - /// - /// Constructs a new instance of a - /// - public SoundIoAudioOut() - { - _audioContext = new SoundIO(); - - _audioContext.Connect(); - _audioContext.FlushEvents(); - - _audioDevice = FindNonRawDefaultAudioDevice(_audioContext, true); - _trackPool = new SoundIoAudioTrackPool(_audioContext, _audioDevice, MaximumTracks); - } - - public bool SupportsChannelCount(int channels) - { - return _audioDevice.SupportsChannelCount(channels); - } - - /// - /// Creates a new audio track with the specified parameters - /// - /// The requested sample rate - /// The requested hardware channels - /// The requested virtual channels - /// A that represents the delegate to invoke when a buffer has been released by the audio track - /// The created track's Track ID - public int OpenHardwareTrack(int sampleRate, int hardwareChannels, int virtualChannels, ReleaseCallback callback) - { - if (!_trackPool.TryGet(out SoundIoAudioTrack track)) - { - return -1; - } - - // Open the output. We currently only support 16-bit signed LE - track.Open(sampleRate, hardwareChannels, virtualChannels, callback, SoundIOFormat.S16LE); - - return track.TrackID; - } - - /// - /// Stops playback and closes the track specified by - /// - /// The ID of the track to close - public void CloseTrack(int trackId) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - // Close and dispose of the track - track.Close(); - - // Recycle the track back into the pool - _trackPool.Put(track); - } - } - - /// - /// Returns a value indicating whether the specified buffer is currently reserved by the specified track - /// - /// The track to check - /// The buffer tag to check - public bool ContainsBuffer(int trackId, long bufferTag) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - return track.ContainsBuffer(bufferTag); - } - - return false; - } - - /// - /// Gets a list of buffer tags the specified track is no longer reserving - /// - /// The track to retrieve buffer tags from - /// The maximum amount of buffer tags to retrieve - /// Buffers released by the specified track - public long[] GetReleasedBuffers(int trackId, int maxCount) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - List bufferTags = new List(); - - while(maxCount-- > 0 && track.ReleasedBuffers.TryDequeue(out long tag)) - { - bufferTags.Add(tag); - } - - return bufferTags.ToArray(); - } - - return new long[0]; - } - - /// - /// Appends an audio buffer to the specified track - /// - /// The sample type of the buffer - /// The track to append the buffer to - /// The internal tag of the buffer - /// The buffer to append to the track - public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - track.AppendBuffer(bufferTag, buffer); - } - } - - /// - /// Starts playback - /// - /// The ID of the track to start playback on - public void Start(int trackId) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - track.Start(); - } - } - - /// - /// Stops playback - /// - /// The ID of the track to stop playback on - public void Stop(int trackId) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - track.Stop(); - } - } - - /// - /// Get track buffer count - /// - /// The ID of the track to get buffer count - public uint GetBufferCount(int trackId) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - return track.BufferCount; - } - - return 0; - } - - /// - /// Get track played sample count - /// - /// The ID of the track to get played sample - public ulong GetPlayedSampleCount(int trackId) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - return track.PlayedSampleCount; - } - - return 0; - } - - /// - /// Flush all track buffers - /// - /// The ID of the track to flush - public bool FlushBuffers(int trackId) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - return track.FlushBuffers(); - } - - return false; - } - - /// - /// Set track volume - /// - /// The volume of the playback - public void SetVolume(int trackId, float volume) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - track.AudioStream.SetVolume(volume); - } - } - - /// - /// Get track volume - /// - public float GetVolume(int trackId) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - return track.AudioStream.Volume; - } - - return 1.0f; - } - - /// - /// Gets the current playback state of the specified track - /// - /// The track to retrieve the playback state for - public PlaybackState GetState(int trackId) - { - if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - return track.State; - } - - return PlaybackState.Stopped; - } - - /// - /// Releases the unmanaged resources used by the - /// - public void Dispose() - { - _trackPool.Dispose(); - _audioContext.Disconnect(); - _audioContext.Dispose(); - } - - /// - /// Searches for a shared version of the default audio device - /// - /// The audio context - /// Whether to fallback to the raw default audio device if a non-raw device cannot be found - private static SoundIODevice FindNonRawDefaultAudioDevice(SoundIO audioContext, bool fallback = false) - { - SoundIODevice defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex); - - if (!defaultAudioDevice.IsRaw) - { - return defaultAudioDevice; - } - - for (int i = 0; i < audioContext.BackendCount; i++) - { - SoundIODevice audioDevice = audioContext.GetOutputDevice(i); - - if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw) - { - return audioDevice; - } - } - - return fallback ? defaultAudioDevice : null; - } - - /// - /// Determines if SoundIO can connect to a supported backend - /// - /// - private static bool IsSupportedInternal() - { - SoundIO context = null; - SoundIODevice device = null; - SoundIOOutStream stream = null; - - bool backendDisconnected = false; - - try - { - context = new SoundIO(); - - context.OnBackendDisconnect = (i) => { - backendDisconnected = true; - }; - - context.Connect(); - context.FlushEvents(); - - if (backendDisconnected) - { - return false; - } - - if (context.OutputDeviceCount == 0) - { - return false; - } - - device = FindNonRawDefaultAudioDevice(context); - - if (device == null || backendDisconnected) - { - return false; - } - - stream = device.CreateOutStream(); - - if (stream == null || backendDisconnected) - { - return false; - } - - return true; - } - catch - { - return false; - } - finally - { - if (stream != null) - { - stream.Dispose(); - } - - if (context != null) - { - context.Dispose(); - } - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs deleted file mode 100644 index 95f181dc..00000000 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs +++ /dev/null @@ -1,193 +0,0 @@ -using SoundIOSharp; -using System; -using System.Collections.Concurrent; -using System.Linq; - -namespace Ryujinx.Audio.SoundIo -{ - /// - /// An object pool containing a set of audio tracks - /// - internal class SoundIoAudioTrackPool : IDisposable - { - /// - /// The current size of the - /// - private int m_Size; - - /// - /// The maximum size of the - /// - private int m_MaxSize; - - /// - /// The audio context this track pool belongs to - /// - private SoundIO m_Context; - - /// - /// The audio device this track pool belongs to - /// - private SoundIODevice m_Device; - - /// - /// The queue that keeps track of the available in the pool. - /// - private ConcurrentQueue m_Queue; - - /// - /// The dictionary providing mapping between a TrackID and - /// - private ConcurrentDictionary m_TrackList; - - /// - /// Gets the current size of the - /// - public int Size { get => m_Size; } - - /// - /// Gets the maximum size of the - /// - public int MaxSize { get => m_MaxSize; } - - /// - /// Gets a value that indicates whether the is empty - /// - public bool IsEmpty { get => m_Queue.IsEmpty; } - - /// - /// Constructs a new instance of a that is empty - /// - /// The maximum amount of tracks that can be created - public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize) - { - m_Size = 0; - m_Context = context; - m_Device = device; - m_MaxSize = maxSize; - - m_Queue = new ConcurrentQueue(); - m_TrackList = new ConcurrentDictionary(); - } - - /// - /// Constructs a new instance of a that contains - /// the specified amount of - /// - /// The maximum amount of tracks that can be created - /// The initial number of tracks that the pool contains - public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize, int initialCapacity) - : this(context, device, maxSize) - { - var trackCollection = Enumerable.Range(0, initialCapacity) - .Select(TrackFactory); - - m_Size = initialCapacity; - m_Queue = new ConcurrentQueue(trackCollection); - } - - /// - /// Creates a new with the proper AudioContext and AudioDevice - /// and the specified - /// - /// The ID of the track to be created - /// A new AudioTrack with the specified ID - private SoundIoAudioTrack TrackFactory(int trackId) - { - // Create a new AudioTrack - SoundIoAudioTrack track = new SoundIoAudioTrack(trackId, m_Context, m_Device); - - // Keep track of issued tracks - m_TrackList[trackId] = track; - - return track; - } - - /// - /// Retrieves a from the pool - /// - /// An AudioTrack from the pool - public SoundIoAudioTrack Get() - { - // If we have a track available, reuse it - if (m_Queue.TryDequeue(out SoundIoAudioTrack track)) - { - return track; - } - - // Have we reached the maximum size of our pool? - if (m_Size >= m_MaxSize) - { - return null; - } - - // We don't have any pooled tracks, so create a new one - return TrackFactory(m_Size++); - } - - /// - /// Retrieves the associated with the specified from the pool - /// - /// The ID of the track to retrieve - public SoundIoAudioTrack Get(int trackId) - { - if (m_TrackList.TryGetValue(trackId, out SoundIoAudioTrack track)) - { - return track; - } - - return null; - } - - /// - /// Attempts to get a from the pool - /// - /// The track retrieved from the pool - /// True if retrieve was successful - public bool TryGet(out SoundIoAudioTrack track) - { - track = Get(); - - return track != null; - } - - /// - /// Attempts to get the associated with the specified from the pool - /// - /// The ID of the track to retrieve - /// The track retrieved from the pool - public bool TryGet(int trackId, out SoundIoAudioTrack track) - { - return m_TrackList.TryGetValue(trackId, out track); - } - - /// - /// Returns an back to the pool for reuse - /// - /// The track to be returned to the pool - public void Put(SoundIoAudioTrack track) - { - // Ensure the track is disposed and not playing audio - track.Close(); - - // Requeue the track for reuse later - m_Queue.Enqueue(track); - } - - /// - /// Releases the unmanaged resources used by the - /// - public void Dispose() - { - foreach (var track in m_TrackList) - { - track.Value.Close(); - track.Value.Dispose(); - } - - m_Size = 0; - m_Queue.Clear(); - m_TrackList.Clear(); - } - } -} diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs deleted file mode 100644 index 2a6190b5..00000000 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs +++ /dev/null @@ -1,29 +0,0 @@ -namespace Ryujinx.Audio.SoundIo -{ - /// - /// Represents the remaining bytes left buffered for a specific buffer tag - /// - internal class SoundIoBuffer - { - /// - /// The buffer tag this represents - /// - public long Tag { get; private set; } - - /// - /// The remaining bytes still to be released - /// - public int Length { get; set; } - - /// - /// Constructs a new instance of a - /// - /// The buffer tag - /// The size of the buffer - public SoundIoBuffer(long tag, int length) - { - Tag = tag; - Length = length; - } - } -} diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs deleted file mode 100644 index b2885021..00000000 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; - -namespace Ryujinx.Audio.SoundIo -{ - /// - /// A thread-safe variable-size circular buffer - /// - internal class SoundIoRingBuffer - { - private byte[] m_Buffer; - private int m_Size; - private int m_HeadOffset; - private int m_TailOffset; - - /// - /// Gets the available bytes in the ring buffer - /// - public int Length - { - get { return m_Size; } - } - - /// - /// Constructs a new instance of a - /// - public SoundIoRingBuffer() - { - m_Buffer = new byte[2048]; - } - - /// - /// Constructs a new instance of a with the specified capacity - /// - /// The number of entries that the can initially contain - public SoundIoRingBuffer(int capacity) - { - m_Buffer = new byte[capacity]; - } - - /// - /// Clears the ring buffer - /// - public void Clear() - { - m_Size = 0; - m_HeadOffset = 0; - m_TailOffset = 0; - } - - /// - /// Clears the specified amount of bytes from the ring buffer - /// - /// The amount of bytes to clear from the ring buffer - public void Clear(int size) - { - lock (this) - { - if (size > m_Size) - { - size = m_Size; - } - - if (size == 0) - { - return; - } - - m_HeadOffset = (m_HeadOffset + size) % m_Buffer.Length; - m_Size -= size; - - if (m_Size == 0) - { - m_HeadOffset = 0; - m_TailOffset = 0; - } - - return; - } - } - - /// - /// Extends the capacity of the ring buffer - /// - private void SetCapacity(int capacity) - { - byte[] buffer = new byte[capacity]; - - if (m_Size > 0) - { - if (m_HeadOffset < m_TailOffset) - { - Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, 0, m_Size); - } - else - { - Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, 0, m_Buffer.Length - m_HeadOffset); - Buffer.BlockCopy(m_Buffer, 0, buffer, m_Buffer.Length - m_HeadOffset, m_TailOffset); - } - } - - m_Buffer = buffer; - m_HeadOffset = 0; - m_TailOffset = m_Size; - } - - - /// - /// Writes a sequence of bytes to the ring buffer - /// - /// A byte array containing the data to write - /// The zero-based byte offset in from which to begin copying bytes to the ring buffer - /// The number of bytes to write - public void Write(T[] buffer, int index, int count) - { - if (count == 0) - { - return; - } - - lock (this) - { - if ((m_Size + count) > m_Buffer.Length) - { - SetCapacity((m_Size + count + 2047) & ~2047); - } - - if (m_HeadOffset < m_TailOffset) - { - int tailLength = m_Buffer.Length - m_TailOffset; - - if (tailLength >= count) - { - Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, count); - } - else - { - Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, tailLength); - Buffer.BlockCopy(buffer, index + tailLength, m_Buffer, 0, count - tailLength); - } - } - else - { - Buffer.BlockCopy(buffer, index, m_Buffer, m_TailOffset, count); - } - - m_Size += count; - m_TailOffset = (m_TailOffset + count) % m_Buffer.Length; - } - } - - /// - /// Reads a sequence of bytes from the ring buffer and advances the position within the ring buffer by the number of bytes read - /// - /// The buffer to write the data into - /// The zero-based byte offset in at which the read bytes will be placed - /// The maximum number of bytes to read - /// The total number of bytes read into the buffer. This might be less than the number of bytes requested if that number of bytes are not currently available, or zero if the ring buffer is empty - public int Read(T[] buffer, int index, int count) - { - lock (this) - { - if (count > m_Size) - { - count = m_Size; - } - - if (count == 0) - { - return 0; - } - - if (m_HeadOffset < m_TailOffset) - { - Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, count); - } - else - { - int tailLength = m_Buffer.Length - m_HeadOffset; - - if (tailLength >= count) - { - Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, count); - } - else - { - Buffer.BlockCopy(m_Buffer, m_HeadOffset, buffer, index, tailLength); - Buffer.BlockCopy(m_Buffer, 0, buffer, index + tailLength, count - tailLength); - } - } - - m_Size -= count; - m_HeadOffset = (m_HeadOffset + count) % m_Buffer.Length; - - if (m_Size == 0) - { - m_HeadOffset = 0; - m_TailOffset = 0; - } - - return count; - } - } - } -} diff --git a/Ryujinx.Audio/ResultCode.cs b/Ryujinx.Audio/ResultCode.cs new file mode 100644 index 00000000..6708cdc1 --- /dev/null +++ b/Ryujinx.Audio/ResultCode.cs @@ -0,0 +1,38 @@ +// +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see . +// + +namespace Ryujinx.Audio +{ + public enum ResultCode + { + ModuleId = 153, + ErrorCodeShift = 9, + + Success = 0, + + DeviceNotFound = (1 << ErrorCodeShift) | ModuleId, + OperationFailed = (2 << ErrorCodeShift) | ModuleId, + UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId, + WorkBufferTooSmall = (4 << ErrorCodeShift) | ModuleId, + BufferRingFull = (8 << ErrorCodeShift) | ModuleId, + UnsupportedChannelConfiguration = (10 << ErrorCodeShift) | ModuleId, + InvalidUpdateInfo = (41 << ErrorCodeShift) | ModuleId, + InvalidAddressInfo = (42 << ErrorCodeShift) | ModuleId, + InvalidMixSorting = (43 << ErrorCodeShift) | ModuleId, + UnsupportedOperation = (513 << ErrorCodeShift) | ModuleId, + } +} diff --git a/Ryujinx.Audio/Ryujinx.Audio.csproj b/Ryujinx.Audio/Ryujinx.Audio.csproj index 6e0b668a..ccdeae3e 100644 --- a/Ryujinx.Audio/Ryujinx.Audio.csproj +++ b/Ryujinx.Audio/Ryujinx.Audio.csproj @@ -5,27 +5,10 @@ true - - - - - - - - - PreserveNewest - libsoundio.dll - - - PreserveNewest - libsoundio.dylib - - - PreserveNewest - libsoundio.so - + + diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 9309ae41..16b4c376 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -2,9 +2,11 @@ using LibHac; using LibHac.Bcat; using LibHac.Fs; using LibHac.FsSystem; -using Ryujinx.Audio.Renderer; +using Ryujinx.Audio; +using Ryujinx.Audio.Input; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Output; using Ryujinx.Audio.Renderer.Device; -using Ryujinx.Audio.Renderer.Integration; using Ryujinx.Audio.Renderer.Server; using Ryujinx.Common; using Ryujinx.Configuration; @@ -51,6 +53,9 @@ namespace Ryujinx.HLE.HOS internal Switch Device { get; private set; } internal SurfaceFlinger SurfaceFlinger { get; private set; } + internal AudioManager AudioManager { get; private set; } + internal AudioOutputManager AudioOutputManager { get; private set; } + internal AudioInputManager AudioInputManager { get; private set; } internal AudioRendererManager AudioRendererManager { get; private set; } internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; } @@ -206,29 +211,48 @@ namespace Ryujinx.HLE.HOS private void InitializeAudioRenderer() { + AudioManager = new AudioManager(); + AudioOutputManager = new AudioOutputManager(); + AudioInputManager = new AudioInputManager(); AudioRendererManager = new AudioRendererManager(); AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(); - IWritableEvent[] writableEvents = new IWritableEvent[RendererConstants.AudioRendererSessionCountMax]; + IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax]; - for (int i = 0; i < writableEvents.Length; i++) + for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++) + { + KEvent registerBufferEvent = new KEvent(KernelContext); + + audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); + } + + AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents); + + IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax]; + + for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++) + { + KEvent registerBufferEvent = new KEvent(KernelContext); + + audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); + } + + AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents); + + IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax]; + + for (int i = 0; i < systemEvents.Length; i++) { KEvent systemEvent = new KEvent(KernelContext); - writableEvents[i] = new AudioKernelEvent(systemEvent); + systemEvents[i] = new AudioKernelEvent(systemEvent); } - HardwareDevice[] devices = new HardwareDevice[RendererConstants.AudioRendererSessionCountMax]; + AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update); - // TODO: don't hardcode those values. - // TODO: keep the device somewhere and dispose it when exiting. - // TODO: This is kind of wrong, we should have an high level API for that and mix all buffers between them. - for (int i = 0; i < devices.Length; i++) - { - devices[i] = new AalHardwareDevice(i, Device.AudioOut, 2, RendererConstants.TargetSampleRate); - } + AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver); - AudioRendererManager.Initialize(writableEvents, devices); + AudioManager.Start(); } public void InitializeServices() @@ -363,6 +387,10 @@ namespace Ryujinx.HLE.HOS // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade. INvDrvServices.Destroy(); + AudioManager.Dispose(); + AudioOutputManager.Dispose(); + AudioInputManager.Dispose(); + AudioRendererManager.Dispose(); KernelContext.Dispose(); diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs new file mode 100644 index 00000000..ee85ded9 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs @@ -0,0 +1,108 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Input; +using Ryujinx.Audio.Integration; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn +{ + class AudioIn : IAudioIn + { + private AudioInputSystem _system; + private uint _processHandle; + private KernelContext _kernelContext; + + public AudioIn(AudioInputSystem system, KernelContext kernelContext, uint processHandle) + { + _system = system; + _kernelContext = kernelContext; + _processHandle = processHandle; + } + + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer) + { + return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer); + } + + public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle) + { + return (ResultCode)_system.AppendUacBuffer(bufferTag, ref buffer, handle); + } + + public bool ContainsBuffer(ulong bufferTag) + { + return _system.ContainsBuffer(bufferTag); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _system.Dispose(); + + _kernelContext.Syscall.CloseHandle((int)_processHandle); + } + } + + public bool FlushBuffers() + { + return _system.FlushBuffers(); + } + + public uint GetBufferCount() + { + return _system.GetBufferCount(); + } + + public ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount) + { + return (ResultCode)_system.GetReleasedBuffers(releasedBuffers, out releasedCount); + } + + public AudioDeviceState GetState() + { + return _system.GetState(); + } + + public float GetVolume() + { + return _system.GetVolume(); + } + + public KEvent RegisterBufferEvent() + { + IWritableEvent outEvent = _system.RegisterBufferEvent(); + + if (outEvent is AudioKernelEvent) + { + return ((AudioKernelEvent)outEvent).Event; + } + else + { + throw new NotImplementedException(); + } + } + + public void SetVolume(float volume) + { + _system.SetVolume(volume); + } + + public ResultCode Start() + { + return (ResultCode)_system.Start(); + } + + public ResultCode Stop() + { + return (ResultCode)_system.Stop(); + } + } +} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs new file mode 100644 index 00000000..1a1a3b6e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs @@ -0,0 +1,209 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn +{ + class AudioInServer : IpcService, IDisposable + { + private IAudioIn _impl; + + public AudioInServer(IAudioIn impl) + { + _impl = impl; + } + + [Command(0)] + // GetAudioInState() -> u32 state + public ResultCode GetAudioInState(ServiceCtx context) + { + context.ResponseData.Write((uint)_impl.GetState()); + + return ResultCode.Success; + } + + [Command(1)] + // Start() + public ResultCode Start(ServiceCtx context) + { + return _impl.Start(); + } + + [Command(2)] + // Stop() + public ResultCode StopAudioIn(ServiceCtx context) + { + return _impl.Stop(); + } + + [Command(3)] + // AppendAudioInBuffer(u64 tag, buffer) + public ResultCode AppendAudioInBuffer(ServiceCtx context) + { + long position = context.Request.SendBuff[0].Position; + + ulong bufferTag = context.RequestData.ReadUInt64(); + + AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); + + return _impl.AppendBuffer(bufferTag, ref data); + } + + [Command(4)] + // RegisterBufferEvent() -> handle + public ResultCode RegisterBufferEvent(ServiceCtx context) + { + KEvent bufferEvent = _impl.RegisterBufferEvent(); + + if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(5)] + // GetReleasedAudioInBuffers() -> (u32 count, buffer tags) + public ResultCode GetReleasedAudioInBuffers(ServiceCtx context) + { + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + using (WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size)) + { + ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast(outputRegion.Memory.Span), out uint releasedCount); + + context.ResponseData.Write(releasedCount); + + return result; + } + } + + [Command(6)] + // ContainsAudioInBuffer(u64 tag) -> b8 + public ResultCode ContainsAudioInBuffer(ServiceCtx context) + { + ulong bufferTag = context.RequestData.ReadUInt64(); + + context.ResponseData.Write(_impl.ContainsBuffer(bufferTag)); + + return ResultCode.Success; + } + + [Command(7)] // 3.0.0+ + // AppendUacInBuffer(u64 tag, handle, buffer) + public ResultCode AppendUacInBuffer(ServiceCtx context) + { + long position = context.Request.SendBuff[0].Position; + + ulong bufferTag = context.RequestData.ReadUInt64(); + uint handle = (uint)context.Request.HandleDesc.ToCopy[0]; + + AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); + + return _impl.AppendUacBuffer(bufferTag, ref data, handle); + } + + [Command(8)] // 3.0.0+ + // AppendAudioInBufferAuto(u64 tag, buffer) + public ResultCode AppendAudioInBufferAuto(ServiceCtx context) + { + (long position, _) = context.Request.GetBufferType0x21(); + + ulong bufferTag = context.RequestData.ReadUInt64(); + + AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); + + return _impl.AppendBuffer(bufferTag, ref data); + } + + [Command(9)] // 3.0.0+ + // GetReleasedAudioInBuffersAuto() -> (u32 count, buffer tags) + public ResultCode GetReleasedAudioInBuffersAuto(ServiceCtx context) + { + (long position, long size) = context.Request.GetBufferType0x22(); + + using (WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size)) + { + ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast(outputRegion.Memory.Span), out uint releasedCount); + + context.ResponseData.Write(releasedCount); + + return result; + } + } + + [Command(10)] // 3.0.0+ + // AppendUacInBufferAuto(u64 tag, handle, buffer) + public ResultCode AppendUacInBufferAuto(ServiceCtx context) + { + (long position, _) = context.Request.GetBufferType0x21(); + + ulong bufferTag = context.RequestData.ReadUInt64(); + uint handle = (uint)context.Request.HandleDesc.ToCopy[0]; + + AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); + + return _impl.AppendUacBuffer(bufferTag, ref data, handle); + } + + [Command(11)] // 4.0.0+ + // GetAudioInBufferCount() -> u32 + public ResultCode GetAudioInBufferCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetBufferCount()); + + return ResultCode.Success; + } + + [Command(12)] // 4.0.0+ + // SetAudioInVolume(s32) + public ResultCode SetAudioInVolume(ServiceCtx context) + { + float volume = context.RequestData.ReadSingle(); + + _impl.SetVolume(volume); + + return ResultCode.Success; + } + + [Command(13)] // 4.0.0+ + // GetAudioInVolume() -> s32 + public ResultCode GetAudioInVolume(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetVolume()); + + return ResultCode.Success; + } + + [Command(14)] // 6.0.0+ + // FlushAudioInBuffers() -> b8 + public ResultCode FlushAudioInBuffers(ServiceCtx context) + { + context.ResponseData.Write(_impl.FlushBuffers()); + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs new file mode 100644 index 00000000..b5073fce --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs @@ -0,0 +1,34 @@ +using Ryujinx.Audio.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn +{ + interface IAudioIn : IDisposable + { + AudioDeviceState GetState(); + + ResultCode Start(); + + ResultCode Stop(); + + ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer); + + // NOTE: This is broken by design... not quite sure what it's used for (if anything in production). + ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle); + + KEvent RegisterBufferEvent(); + + ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount); + + bool ContainsBuffer(ulong bufferTag); + + uint GetBufferCount(); + + bool FlushBuffers(); + + void SetVolume(float volume); + + float GetVolume(); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs new file mode 100644 index 00000000..2d342206 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs @@ -0,0 +1,41 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Input; +using Ryujinx.HLE.HOS.Services.Audio.AudioIn; + +using AudioInManagerImpl = Ryujinx.Audio.Input.AudioInputManager; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + class AudioInManager : IAudioInManager + { + private AudioInManagerImpl _impl; + + public AudioInManager(AudioInManagerImpl impl) + { + _impl = impl; + } + + public string[] ListAudioIns(bool filtered) + { + return _impl.ListAudioIns(filtered); + } + + public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle) + { + var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory; + + ResultCode result = (ResultCode)_impl.OpenAudioIn(out outputDeviceName, out outputConfiguration, out AudioInputSystem inSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle); + + if (result == ResultCode.Success) + { + obj = new AudioIn.AudioIn(inSystem, context.Device.System.KernelContext, processHandle); + } + else + { + obj = null; + } + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs new file mode 100644 index 00000000..079b91ca --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs @@ -0,0 +1,235 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Audio.AudioIn; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audin:u")] + class AudioInManagerServer : IpcService + { + private const int AudioInNameSize = 0x100; + + private IAudioInManager _impl; + + public AudioInManagerServer(ServiceCtx context) : this(context, new AudioInManager(context.Device.System.AudioInputManager)) { } + + public AudioInManagerServer(ServiceCtx context, IAudioInManager impl) : base(context.Device.System.AudOutServer) + { + _impl = impl; + } + + [Command(0)] + // ListAudioIns() -> (u32, buffer) + public ResultCode ListAudioIns(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioIns(false); + + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioInNameSize - buffer.Length); + + position += AudioInNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(1)] + // OpenAudioIn(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle, buffer name) + // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name) + public ResultCode OpenAudioIn(ServiceCtx context) + { + AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + long deviceNameInputPosition = context.Request.SendBuff[0].Position; + long deviceNameInputSize = context.Request.SendBuff[0].Size; + + long deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; + long deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, deviceNameInputSize); + + ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(outputConfiguration); + + byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); + + context.Memory.Write((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioInServer(obj)); + } + + return resultCode; + } + + [Command(2)] // 3.0.0+ + // ListAudioInsAuto() -> (u32, buffer) + public ResultCode ListAudioInsAuto(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioIns(false); + + (long position, long size) = context.Request.GetBufferType0x22(); + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioInNameSize - buffer.Length); + + position += AudioInNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(3)] // 3.0.0+ + // OpenAudioInAuto(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle, buffer) + // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name) + public ResultCode OpenAudioInAuto(ServiceCtx context) + { + AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + (long deviceNameInputPosition, long deviceNameInputSize) = context.Request.GetBufferType0x21(); + (long deviceNameOutputPosition, long deviceNameOutputSize) = context.Request.GetBufferType0x22(); + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, deviceNameInputSize); + + ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(outputConfiguration); + + byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); + + context.Memory.Write((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioInServer(obj)); + } + + return resultCode; + } + + [Command(4)] // 3.0.0+ + // ListAudioInsAutoFiltered() -> (u32, buffer) + public ResultCode ListAudioInsAutoFiltered(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioIns(true); + + (long position, long size) = context.Request.GetBufferType0x22(); + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioInNameSize - buffer.Length); + + position += AudioInNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(5)] // 5.0.0+ + // OpenAudioInProtocolSpecified(b64 protocol_specified_related, AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle, buffer name) + // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name) + public ResultCode OpenAudioInProtocolSpecified(ServiceCtx context) + { + // NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices). + bool protocolSpecifiedRelated = context.RequestData.ReadUInt64() == 1; + + AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + long deviceNameInputPosition = context.Request.SendBuff[0].Position; + long deviceNameInputSize = context.Request.SendBuff[0].Size; + + long deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; + long deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, deviceNameInputSize); + + ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(outputConfiguration); + + byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); + + context.Memory.Write((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioInServer(obj)); + } + + return resultCode; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs new file mode 100644 index 00000000..f2588452 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs @@ -0,0 +1,108 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Output; +using Ryujinx.HLE.HOS.Kernel; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut +{ + class AudioOut : IAudioOut + { + private AudioOutputSystem _system; + private uint _processHandle; + private KernelContext _kernelContext; + + public AudioOut(AudioOutputSystem system, KernelContext kernelContext, uint processHandle) + { + _system = system; + _kernelContext = kernelContext; + _processHandle = processHandle; + } + + public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer) + { + return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer); + } + + public bool ContainsBuffer(ulong bufferTag) + { + return _system.ContainsBuffer(bufferTag); + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _system.Dispose(); + + _kernelContext.Syscall.CloseHandle((int)_processHandle); + } + } + + public bool FlushBuffers() + { + return _system.FlushBuffers(); + } + + public uint GetBufferCount() + { + return _system.GetBufferCount(); + } + + public ulong GetPlayedSampleCount() + { + return _system.GetPlayedSampleCount(); + } + + public ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount) + { + return (ResultCode)_system.GetReleasedBuffer(releasedBuffers, out releasedCount); + } + + public AudioDeviceState GetState() + { + return _system.GetState(); + } + + public float GetVolume() + { + return _system.GetVolume(); + } + + public KEvent RegisterBufferEvent() + { + IWritableEvent outEvent = _system.RegisterBufferEvent(); + + if (outEvent is AudioKernelEvent) + { + return ((AudioKernelEvent)outEvent).Event; + } + else + { + throw new NotImplementedException(); + } + } + + public void SetVolume(float volume) + { + _system.SetVolume(volume); + } + + public ResultCode Start() + { + return (ResultCode)_system.Start(); + } + + public ResultCode Stop() + { + return (ResultCode)_system.Stop(); + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs new file mode 100644 index 00000000..4242eb7e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs @@ -0,0 +1,190 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut +{ + class AudioOutServer : IpcService, IDisposable + { + private IAudioOut _impl; + + public AudioOutServer(IAudioOut impl) + { + _impl = impl; + } + + [Command(0)] + // GetAudioOutState() -> u32 state + public ResultCode GetAudioOutState(ServiceCtx context) + { + context.ResponseData.Write((uint)_impl.GetState()); + + return ResultCode.Success; + } + + [Command(1)] + // Start() + public ResultCode Start(ServiceCtx context) + { + return _impl.Start(); + } + + [Command(2)] + // Stop() + public ResultCode Stop(ServiceCtx context) + { + return _impl.Stop(); + } + + [Command(3)] + // AppendAudioOutBuffer(u64 bufferTag, buffer buffer) + public ResultCode AppendAudioOutBuffer(ServiceCtx context) + { + long position = context.Request.SendBuff[0].Position; + + ulong bufferTag = context.RequestData.ReadUInt64(); + + AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); + + return _impl.AppendBuffer(bufferTag, ref data); + } + + [Command(4)] + // RegisterBufferEvent() -> handle + public ResultCode RegisterBufferEvent(ServiceCtx context) + { + KEvent bufferEvent = _impl.RegisterBufferEvent(); + + if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(5)] + // GetReleasedAudioOutBuffers() -> (u32 count, buffer tags) + public ResultCode GetReleasedAudioOutBuffers(ServiceCtx context) + { + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + using (WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size)) + { + ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast(outputRegion.Memory.Span), out uint releasedCount); + + context.ResponseData.Write(releasedCount); + + return result; + } + } + + [Command(6)] + // ContainsAudioOutBuffer(u64 tag) -> b8 + public ResultCode ContainsAudioOutBuffer(ServiceCtx context) + { + ulong bufferTag = context.RequestData.ReadUInt64(); + + context.ResponseData.Write(_impl.ContainsBuffer(bufferTag)); + + return ResultCode.Success; + } + + [Command(7)] // 3.0.0+ + // AppendAudioOutBufferAuto(u64 tag, buffer) + public ResultCode AppendAudioOutBufferAuto(ServiceCtx context) + { + (long position, _) = context.Request.GetBufferType0x21(); + + ulong bufferTag = context.RequestData.ReadUInt64(); + + AudioUserBuffer data = MemoryHelper.Read(context.Memory, position); + + return _impl.AppendBuffer(bufferTag, ref data); + } + + [Command(8)] // 3.0.0+ + // GetReleasedAudioOutBuffersAuto() -> (u32 count, buffer tags) + public ResultCode GetReleasedAudioOutBuffersAuto(ServiceCtx context) + { + (long position, long size) = context.Request.GetBufferType0x22(); + + using (WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size)) + { + ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast(outputRegion.Memory.Span), out uint releasedCount); + + context.ResponseData.Write(releasedCount); + + return result; + } + } + + [Command(9)] // 4.0.0+ + // GetAudioOutBufferCount() -> u32 + public ResultCode GetAudioOutBufferCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetBufferCount()); + + return ResultCode.Success; + } + + [Command(10)] // 4.0.0+ + // GetAudioOutPlayedSampleCount() -> u64 + public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetPlayedSampleCount()); + + return ResultCode.Success; + } + + [Command(11)] // 4.0.0+ + // FlushAudioOutBuffers() -> b8 + public ResultCode FlushAudioOutBuffers(ServiceCtx context) + { + context.ResponseData.Write(_impl.FlushBuffers()); + + return ResultCode.Success; + } + + [Command(12)] // 6.0.0+ + // SetAudioOutVolume(s32) + public ResultCode SetAudioOutVolume(ServiceCtx context) + { + float volume = context.RequestData.ReadSingle(); + + _impl.SetVolume(volume); + + return ResultCode.Success; + } + + [Command(13)] // 6.0.0+ + // GetAudioOutVolume() -> s32 + public ResultCode GetAudioOutVolume(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetVolume()); + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs new file mode 100644 index 00000000..8533d3c5 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs @@ -0,0 +1,33 @@ +using Ryujinx.Audio.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut +{ + interface IAudioOut : IDisposable + { + AudioDeviceState GetState(); + + ResultCode Start(); + + ResultCode Stop(); + + ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer); + + KEvent RegisterBufferEvent(); + + ResultCode GetReleasedBuffers(Span releasedBuffers, out uint releasedCount); + + bool ContainsBuffer(ulong bufferTag); + + uint GetBufferCount(); + + ulong GetPlayedSampleCount(); + + bool FlushBuffers(); + + void SetVolume(float volume); + + float GetVolume(); + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs new file mode 100644 index 00000000..29490553 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs @@ -0,0 +1,41 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Output; +using Ryujinx.HLE.HOS.Services.Audio.AudioOut; + +using AudioOutManagerImpl = Ryujinx.Audio.Output.AudioOutputManager; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + class AudioOutManager : IAudioOutManager + { + private AudioOutManagerImpl _impl; + + public AudioOutManager(AudioOutManagerImpl impl) + { + _impl = impl; + } + + public string[] ListAudioOuts() + { + return _impl.ListAudioOuts(); + } + + public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle) + { + var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory; + + ResultCode result = (ResultCode)_impl.OpenAudioOut(out outputDeviceName, out outputConfiguration, out AudioOutputSystem outSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle); + + if (result == ResultCode.Success) + { + obj = new AudioOut.AudioOut(outSystem, context.Device.System.KernelContext, processHandle); + } + else + { + obj = null; + } + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs deleted file mode 100644 index c941cf4f..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs +++ /dev/null @@ -1,228 +0,0 @@ -using Ryujinx.Audio; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.HOS.Kernel.Common; -using Ryujinx.HLE.HOS.Kernel.Threading; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager -{ - class IAudioOut : IpcService, IDisposable - { - private readonly KernelContext _kernelContext; - private readonly IAalOutput _audioOut; - private readonly KEvent _releaseEvent; - private int _releaseEventHandle; - private readonly int _track; - private readonly int _clientHandle; - - public IAudioOut(KernelContext kernelContext, IAalOutput audioOut, KEvent releaseEvent, int track, int clientHandle) - { - _kernelContext = kernelContext; - _audioOut = audioOut; - _releaseEvent = releaseEvent; - _track = track; - _clientHandle = clientHandle; - } - - [Command(0)] - // GetAudioOutState() -> u32 state - public ResultCode GetAudioOutState(ServiceCtx context) - { - context.ResponseData.Write((int)_audioOut.GetState(_track)); - - return ResultCode.Success; - } - - [Command(1)] - // StartAudioOut() - public ResultCode StartAudioOut(ServiceCtx context) - { - _audioOut.Start(_track); - - return ResultCode.Success; - } - - [Command(2)] - // StopAudioOut() - public ResultCode StopAudioOut(ServiceCtx context) - { - _audioOut.Stop(_track); - - return ResultCode.Success; - } - - [Command(3)] - // AppendAudioOutBuffer(u64 tag, buffer) - public ResultCode AppendAudioOutBuffer(ServiceCtx context) - { - return AppendAudioOutBufferImpl(context, context.Request.SendBuff[0].Position); - } - - [Command(4)] - // RegisterBufferEvent() -> handle - public ResultCode RegisterBufferEvent(ServiceCtx context) - { - if (_releaseEventHandle == 0) - { - if (context.Process.HandleTable.GenerateHandle(_releaseEvent.ReadableEvent, out _releaseEventHandle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_releaseEventHandle); - - return ResultCode.Success; - } - - [Command(5)] - // GetReleasedAudioOutBuffer() -> (u32 count, buffer) - public ResultCode GetReleasedAudioOutBuffer(ServiceCtx context) - { - long position = context.Request.ReceiveBuff[0].Position; - long size = context.Request.ReceiveBuff[0].Size; - - return GetReleasedAudioOutBufferImpl(context, position, size); - } - - [Command(6)] - // ContainsAudioOutBuffer(u64 tag) -> b8 - public ResultCode ContainsAudioOutBuffer(ServiceCtx context) - { - long tag = context.RequestData.ReadInt64(); - - context.ResponseData.Write(_audioOut.ContainsBuffer(_track, tag)); - - return ResultCode.Success; - } - - [Command(7)] // 3.0.0+ - // AppendAudioOutBufferAuto(u64 tag, buffer) - public ResultCode AppendAudioOutBufferAuto(ServiceCtx context) - { - (long position, _) = context.Request.GetBufferType0x21(); - - return AppendAudioOutBufferImpl(context, position); - } - - public ResultCode AppendAudioOutBufferImpl(ServiceCtx context, long position) - { - long tag = context.RequestData.ReadInt64(); - - AudioOutData data = MemoryHelper.Read(context.Memory, position); - - // NOTE: Assume PCM16 all the time, change if new format are found. - short[] buffer = new short[data.SampleBufferSize / sizeof(short)]; - - context.Process.HandleTable.GetKProcess(_clientHandle).CpuMemory.Read((ulong)data.SampleBufferPtr, MemoryMarshal.Cast(buffer)); - - _audioOut.AppendBuffer(_track, tag, buffer); - - return ResultCode.Success; - } - - [Command(8)] // 3.0.0+ - // GetReleasedAudioOutBufferAuto() -> (u32 count, buffer) - public ResultCode GetReleasedAudioOutBufferAuto(ServiceCtx context) - { - (long position, long size) = context.Request.GetBufferType0x22(); - - return GetReleasedAudioOutBufferImpl(context, position, size); - } - - public ResultCode GetReleasedAudioOutBufferImpl(ServiceCtx context, long position, long size) - { - uint count = (uint)((ulong)size >> 3); - - long[] releasedBuffers = _audioOut.GetReleasedBuffers(_track, (int)count); - - for (uint index = 0; index < count; index++) - { - long tag = 0; - - if (index < releasedBuffers.Length) - { - tag = releasedBuffers[index]; - } - - context.Memory.Write((ulong)(position + index * 8), tag); - } - - context.ResponseData.Write(releasedBuffers.Length); - - return ResultCode.Success; - } - - [Command(9)] // 4.0.0+ - // GetAudioOutBufferCount() -> u32 - public ResultCode GetAudioOutBufferCount(ServiceCtx context) - { - uint bufferCount = _audioOut.GetBufferCount(_track); - - context.ResponseData.Write(bufferCount); - - return ResultCode.Success; - } - - [Command(10)] // 4.0.0+ - // GetAudioOutPlayedSampleCount() -> u64 - public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context) - { - ulong playedSampleCount = _audioOut.GetPlayedSampleCount(_track); - - context.ResponseData.Write(playedSampleCount); - - return ResultCode.Success; - } - - [Command(11)] // 4.0.0+ - // FlushAudioOutBuffers() -> b8 - public ResultCode FlushAudioOutBuffers(ServiceCtx context) - { - bool heldBuffers = _audioOut.FlushBuffers(_track); - - context.ResponseData.Write(heldBuffers); - - return ResultCode.Success; - } - - [Command(12)] // 6.0.0+ - // SetAudioOutVolume(s32) - public ResultCode SetAudioOutVolume(ServiceCtx context) - { - float volume = context.RequestData.ReadSingle(); - - _audioOut.SetVolume(_track, volume); - - return ResultCode.Success; - } - - [Command(13)] // 6.0.0+ - // GetAudioOutVolume() -> s32 - public ResultCode GetAudioOutVolume(ServiceCtx context) - { - float volume = _audioOut.GetVolume(_track); - - context.ResponseData.Write(volume); - - return ResultCode.Success; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _kernelContext.Syscall.CloseHandle(_clientHandle); - _audioOut.CloseTrack(_track); - } - } - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs deleted file mode 100644 index 2598d0f8..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager -{ - [StructLayout(LayoutKind.Sequential)] - struct AudioOutData - { - public long NextBufferPtr; - public long SampleBufferPtr; - public long SampleBufferCapacity; - public long SampleBufferSize; - public long SampleBufferInnerOffset; - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs new file mode 100644 index 00000000..13930d31 --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs @@ -0,0 +1,162 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Common; +using Ryujinx.Common.Logging; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Services.Audio.AudioOut; +using System.Text; + +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audout:u")] + class AudioOutManagerServer : IpcService + { + private const int AudioOutNameSize = 0x100; + + private IAudioOutManager _impl; + + public AudioOutManagerServer(ServiceCtx context) : this(context, new AudioOutManager(context.Device.System.AudioOutputManager)) { } + + public AudioOutManagerServer(ServiceCtx context, IAudioOutManager impl) : base(context.Device.System.AudOutServer) + { + _impl = impl; + } + + [Command(0)] + // ListAudioOuts() -> (u32, buffer) + public ResultCode ListAudioOuts(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioOuts(); + + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioOutNameSize - buffer.Length); + + position += AudioOutNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(1)] + // OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle process_handle, buffer name_in) + // -> (AudioOutInputConfiguration output_config, object, buffer name_out) + public ResultCode OpenAudioOut(ServiceCtx context) + { + AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + long deviceNameInputPosition = context.Request.SendBuff[0].Position; + long deviceNameInputSize = context.Request.SendBuff[0].Size; + + long deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; + long deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, deviceNameInputSize); + + ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(outputConfiguration); + + byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); + + context.Memory.Write((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioOutServer(obj)); + } + + return resultCode; + } + + [Command(2)] // 3.0.0+ + // ListAudioOutsAuto() -> (u32, buffer) + public ResultCode ListAudioOutsAuto(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioOuts(); + + (long position, long size) = context.Request.GetBufferType0x22(); + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioOutNameSize - buffer.Length); + + position += AudioOutNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(3)] // 3.0.0+ + // OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle process_handle, buffer name_in) + // -> (AudioOutInputConfiguration output_config, object, buffer name_out) + public ResultCode OpenAudioOutAuto(ServiceCtx context) + { + AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct(); + ulong appletResourceUserId = context.RequestData.ReadUInt64(); + + (long deviceNameInputPosition, long deviceNameInputSize) = context.Request.GetBufferType0x21(); + (long deviceNameOutputPosition, long deviceNameOutputSize) = context.Request.GetBufferType0x22(); + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, deviceNameInputSize); + + ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(outputConfiguration); + + byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); + + context.Memory.Write((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioOutServer(obj)); + } + + return resultCode; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs deleted file mode 100644 index fdc23604..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Ryujinx.Audio; -using Ryujinx.Audio.Renderer; -using Ryujinx.Audio.Renderer.Integration; -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - public class AalHardwareDevice : HardwareDevice - { - private IAalOutput _output; - private int _trackId; - private int _bufferTag; - private int _nextTag; - private AutoResetEvent _releaseEvent; - - private uint _channelCount; - private uint _sampleRate; - - private short[] _buffer; - - private Queue _releasedTags; - - public AalHardwareDevice(int bufferTag, IAalOutput output, uint channelCount, uint sampleRate) - { - _bufferTag = bufferTag; - _channelCount = channelCount; - _sampleRate = sampleRate; - _output = output; - _releaseEvent = new AutoResetEvent(true); - _trackId = _output.OpenTrack((int)sampleRate, (int)channelCount, AudioCallback); - _releasedTags = new Queue(); - - _buffer = new short[RendererConstants.TargetSampleCount * channelCount]; - - _output.Start(_trackId); - } - - private void AudioCallback() - { - long[] released = _output.GetReleasedBuffers(_trackId, int.MaxValue); - - lock (_releasedTags) - { - foreach (long tag in released) - { - _releasedTags.Enqueue(tag); - } - } - } - - private long GetReleasedTag() - { - lock (_releasedTags) - { - if (_releasedTags.Count > 0) - { - return _releasedTags.Dequeue(); - } - - return (_bufferTag << 16) | (_nextTag++); - } - } - - public void AppendBuffer(ReadOnlySpan data, uint channelCount) - { - data.CopyTo(_buffer.AsSpan()); - - _output.AppendBuffer(_trackId, GetReleasedTag(), _buffer); - } - - public uint GetChannelCount() - { - return _channelCount; - } - - public uint GetSampleRate() - { - return _sampleRate; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _output.Stop(_trackId); - _output.CloseTrack(_trackId); - _releaseEvent.Dispose(); - } - } - } -} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs index 1a132b91..55bf29ae 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs @@ -1,4 +1,4 @@ -using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Integration; using Ryujinx.HLE.HOS.Kernel.Threading; namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs index 702648dd..d69bde03 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs @@ -1,4 +1,4 @@ -using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Server; using Ryujinx.HLE.HOS.Kernel.Threading; using System; diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs index b3f7f5e0..9bbe5b0e 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs @@ -1,87 +1,12 @@ -using Ryujinx.Cpu; -using Ryujinx.Memory; -using System; -using System.Text; +using Ryujinx.Audio.Common; +using Ryujinx.HLE.HOS.Services.Audio.AudioIn; namespace Ryujinx.HLE.HOS.Services.Audio { - [Service("audin:u")] - class IAudioInManager : IpcService + interface IAudioInManager { - private const string DefaultAudioInsName = "BuiltInHeadset"; + public string[] ListAudioIns(bool filtered); - public IAudioInManager(ServiceCtx context) { } - - [Command(0)] - // ListAudioIns() -> (u32 count, buffer names) - public ResultCode ListAudioIns(ServiceCtx context) - { - long bufferPosition = context.Request.ReceiveBuff[0].Position; - long bufferSize = context.Request.ReceiveBuff[0].Size; - - // NOTE: The service check if AudioInManager thread is started, if not it starts it. - - uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, false); - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [Command(2)] // 3.0.0+ - // ListAudioInsAuto() -> (u32 count, buffer names) - public ResultCode ListAudioInsAuto(ServiceCtx context) - { - (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22(); - - // NOTE: The service check if AudioInManager thread is started, if not it starts it. - - uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, false); - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [Command(4)] // 3.0.0+ - // ListAudioInsAutoFiltered() -> (u32 count, buffer names) - public ResultCode ListAudioInsAutoFiltered(ServiceCtx context) - { - (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22(); - - // NOTE: The service check if AudioInManager thread is started, if not it starts it. - - uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, true); - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - private uint ListAudioInsImpl(IVirtualMemoryManager memory, long bufferPosition, long bufferSize, bool filtered = false) - { - uint count = 0; - - MemoryHelper.FillWithZeros(memory, bufferPosition, (int)bufferSize); - - if (bufferSize > 0) - { - // NOTE: The service also check that the input target is enabled when in filtering mode, as audctl and most of the audin logic isn't supported, we don't support it. - if (!filtered) - { - byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioInsName + "\0"); - - memory.Write((ulong)bufferPosition, deviceNameBuffer); - - count++; - } - - // NOTE: The service adds other input devices names available in the buffer, - // every name is aligned to 0x100 bytes. - // Since we don't support it for now, it's fine to do nothing here. - } - - return count; - } + public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle); } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs index 6204a7be..0b164019 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs @@ -1,147 +1,12 @@ -using Ryujinx.Audio; -using Ryujinx.Common.Logging; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Audio.AudioOutManager; -using System.Text; +using Ryujinx.Audio.Common; +using Ryujinx.HLE.HOS.Services.Audio.AudioOut; namespace Ryujinx.HLE.HOS.Services.Audio { - [Service("audout:u")] - class IAudioOutManager : IpcService + interface IAudioOutManager { - private const string DefaultAudioOutput = "DeviceOut"; - private const int DefaultSampleRate = 48000; - private const int DefaultChannelsCount = 2; + public string[] ListAudioOuts(); - public IAudioOutManager(ServiceCtx context) : base(context.Device.System.AudOutServer) { } - - [Command(0)] - // ListAudioOuts() -> (u32 count, buffer) - public ResultCode ListAudioOuts(ServiceCtx context) - { - return ListAudioOutsImpl(context, context.Request.ReceiveBuff[0].Position, context.Request.ReceiveBuff[0].Size); - } - - [Command(1)] - // OpenAudioOut(u32 sample_rate, u16 unused, u16 channel_count, nn::applet::AppletResourceUserId, pid, handle, buffer name_in) - // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name_out) - public ResultCode OpenAudioOut(ServiceCtx context) - { - return OpenAudioOutImpl(context, context.Request.SendBuff[0].Position, context.Request.SendBuff[0].Size, - context.Request.ReceiveBuff[0].Position, context.Request.ReceiveBuff[0].Size); - } - - [Command(2)] // 3.0.0+ - // ListAudioOutsAuto() -> (u32 count, buffer) - public ResultCode ListAudioOutsAuto(ServiceCtx context) - { - (long recvPosition, long recvSize) = context.Request.GetBufferType0x22(); - - return ListAudioOutsImpl(context, recvPosition, recvSize); - } - - [Command(3)] // 3.0.0+ - // OpenAudioOutAuto(u32 sample_rate, u16 unused, u16 channel_count, nn::applet::AppletResourceUserId, pid, handle, buffer) - // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object, buffer name_out) - public ResultCode OpenAudioOutAuto(ServiceCtx context) - { - (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); - (long recvPosition, long recvSize) = context.Request.GetBufferType0x22(); - - return OpenAudioOutImpl(context, sendPosition, sendSize, recvPosition, recvSize); - } - - private ResultCode ListAudioOutsImpl(ServiceCtx context, long position, long size) - { - int nameCount = 0; - - byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioOutput + "\0"); - - if ((ulong)deviceNameBuffer.Length <= (ulong)size) - { - context.Memory.Write((ulong)position, deviceNameBuffer); - - nameCount++; - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - } - - context.ResponseData.Write(nameCount); - - return ResultCode.Success; - } - - private ResultCode OpenAudioOutImpl(ServiceCtx context, long sendPosition, long sendSize, long receivePosition, long receiveSize) - { - string deviceName = MemoryHelper.ReadAsciiString(context.Memory, sendPosition, sendSize); - - if (deviceName == string.Empty) - { - deviceName = DefaultAudioOutput; - } - - if (deviceName != DefaultAudioOutput) - { - Logger.Warning?.Print(LogClass.Audio, "Invalid device name!"); - - return ResultCode.DeviceNotFound; - } - - byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(deviceName + "\0"); - - if ((ulong)deviceNameBuffer.Length <= (ulong)receiveSize) - { - context.Memory.Write((ulong)receivePosition, deviceNameBuffer); - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {receiveSize} too small!"); - } - - int sampleRate = context.RequestData.ReadInt32(); - int channels = context.RequestData.ReadInt32(); - - if (sampleRate == 0) - { - sampleRate = DefaultSampleRate; - } - - if (sampleRate != DefaultSampleRate) - { - Logger.Warning?.Print(LogClass.Audio, "Invalid sample rate!"); - - return ResultCode.UnsupportedSampleRate; - } - - channels = (ushort)channels; - - if (channels == 0) - { - channels = DefaultChannelsCount; - } - - KEvent releaseEvent = new KEvent(context.Device.System.KernelContext); - - ReleaseCallback callback = () => - { - releaseEvent.ReadableEvent.Signal(); - }; - - IAalOutput audioOut = context.Device.AudioOut; - - int track = audioOut.OpenTrack(sampleRate, channels, callback); - - MakeObject(context, new IAudioOut(context.Device.System.KernelContext, audioOut, releaseEvent, track, context.Request.HandleDesc.ToCopy[0])); - - context.ResponseData.Write(sampleRate); - context.ResponseData.Write(channels); - context.ResponseData.Write((int)SampleFormat.PcmInt16); - context.ResponseData.Write((int)PlaybackState.Stopped); - - return ResultCode.Success; - } + public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle); } -} \ No newline at end of file +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs b/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs deleted file mode 100644 index 654436e4..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - enum SampleFormat : byte - { - Invalid = 0, - PcmInt8 = 1, - PcmInt16 = 2, - PcmInt24 = 3, - PcmInt32 = 4, - PcmFloat = 5, - Adpcm = 6 - } -} \ No newline at end of file diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index c3b5ac7a..3d48d893 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -6,7 +6,6 @@ - diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 2306e5d3..865a86d7 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,5 +1,6 @@ using LibHac.FsSystem; -using Ryujinx.Audio; +using Ryujinx.Audio.Backends.CompatLayer; +using Ryujinx.Audio.Integration; using Ryujinx.Common; using Ryujinx.Configuration; using Ryujinx.Graphics.GAL; @@ -22,7 +23,7 @@ namespace Ryujinx.HLE { public class Switch : IDisposable { - public IAalOutput AudioOut { get; private set; } + public IHardwareDeviceDriver AudioDeviceDriver { get; private set; } internal MemoryBlock Memory { get; private set; } @@ -48,16 +49,16 @@ namespace Ryujinx.HLE public bool EnableDeviceVsync { get; set; } = true; - public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, UserChannelPersistence userChannelPersistence, IRenderer renderer, IAalOutput audioOut) + public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, UserChannelPersistence userChannelPersistence, IRenderer renderer, IHardwareDeviceDriver audioDeviceDriver) { if (renderer == null) { throw new ArgumentNullException(nameof(renderer)); } - if (audioOut == null) + if (audioDeviceDriver == null) { - throw new ArgumentNullException(nameof(audioOut)); + throw new ArgumentNullException(nameof(audioDeviceDriver)); } if (userChannelPersistence == null) @@ -67,7 +68,7 @@ namespace Ryujinx.HLE UserChannelPersistence = userChannelPersistence; - AudioOut = audioOut; + AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(audioDeviceDriver); Memory = new MemoryBlock(1UL << 32); @@ -210,7 +211,7 @@ namespace Ryujinx.HLE System.Dispose(); Host1x.Dispose(); - AudioOut.Dispose(); + AudioDeviceDriver.Dispose(); FileSystem.Unload(); Memory.Dispose(); } diff --git a/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs b/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs index e743252d..d2c2e6cb 100644 --- a/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs +++ b/Ryujinx.Tests/Audio/Renderer/Server/PoolMapperTests.cs @@ -1,5 +1,5 @@ using NUnit.Framework; -using Ryujinx.Audio.Renderer; +using Ryujinx.Audio; using Ryujinx.Audio.Renderer.Server.MemoryPool; using System; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; diff --git a/Ryujinx.Tests/Ryujinx.Tests.csproj b/Ryujinx.Tests/Ryujinx.Tests.csproj index e6d7208c..3dfc7405 100644 --- a/Ryujinx.Tests/Ryujinx.Tests.csproj +++ b/Ryujinx.Tests/Ryujinx.Tests.csproj @@ -23,7 +23,7 @@ - + diff --git a/Ryujinx.sln b/Ryujinx.sln index 3e557dea..ee1d57bd 100644 --- a/Ryujinx.sln +++ b/Ryujinx.sln @@ -11,8 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Tests.Unicorn", "Ry EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.HLE", "Ryujinx.HLE\Ryujinx.HLE.csproj", "{CB92CFF9-1D62-4D4F-9E88-8130EF61E351}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{5C1D818E-682A-46A5-9D54-30006E26C270}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.ShaderTools", "Ryujinx.ShaderTools\Ryujinx.ShaderTools.csproj", "{3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Common", "Ryujinx.Common\Ryujinx.Common.csproj", "{5FD4E4F6-8928-4B3C-BE07-28A675C17226}" @@ -31,7 +29,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Shader", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec", "Ryujinx.Graphics.Nvdec\Ryujinx.Graphics.Nvdec.csproj", "{85A0FA56-DC01-4A42-8808-70DAC76BD66D}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Renderer", "Ryujinx.Audio.Renderer\Ryujinx.Audio.Renderer.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio", "Ryujinx.Audio\Ryujinx.Audio.csproj", "{806ACF6D-90B0-45D0-A1AC-5F220F3B3985}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{36F870C1-3E5F-485F-B426-F0645AF78751}" ProjectSection(SolutionItems) = preProject @@ -57,6 +55,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Nvdec.H264 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Graphics.Video", "Ryujinx.Graphics.Video\Ryujinx.Graphics.Video.csproj", "{FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ryujinx.Audio.Backends.OpenAL", "Ryujinx.Audio.Backends.OpenAL\Ryujinx.Audio.Backends.OpenAL.csproj", "{0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}" +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 Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -79,10 +81,6 @@ Global {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Debug|Any CPU.Build.0 = Debug|Any CPU {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.ActiveCfg = Release|Any CPU {CB92CFF9-1D62-4D4F-9E88-8130EF61E351}.Release|Any CPU.Build.0 = Release|Any CPU - {5C1D818E-682A-46A5-9D54-30006E26C270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5C1D818E-682A-46A5-9D54-30006E26C270}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5C1D818E-682A-46A5-9D54-30006E26C270}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5C1D818E-682A-46A5-9D54-30006E26C270}.Release|Any CPU.Build.0 = Release|Any CPU {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Debug|Any CPU.Build.0 = Debug|Any CPU {3AB294D0-2230-468F-9EB3-BDFCAEAE99A5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -159,6 +157,14 @@ Global {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Debug|Any CPU.Build.0 = Debug|Any CPU {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.ActiveCfg = Release|Any CPU {FD4A2C14-8E3D-4957-ABBE-3C38897B3E2D}.Release|Any CPU.Build.0 = Release|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0BE11899-DF2D-4BDE-B9EE-2489E8D35E7D}.Release|Any CPU.Build.0 = Release|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {716364DE-B988-41A6-BAB4-327964266ECC}.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 7f0d23e7..c048eb26 100644 --- a/Ryujinx/Ryujinx.csproj +++ b/Ryujinx/Ryujinx.csproj @@ -23,7 +23,8 @@ - + + @@ -38,9 +39,9 @@ Always - + Always - LICENSE-Ryujinx.Audio.Renderer.txt + LICENSE-Ryujinx.Audio.txt diff --git a/Ryujinx/Ui/MainWindow.cs b/Ryujinx/Ui/MainWindow.cs index 828fe853..809b693b 100644 --- a/Ryujinx/Ui/MainWindow.cs +++ b/Ryujinx/Ui/MainWindow.cs @@ -4,6 +4,10 @@ using Gtk; using LibHac.Common; using LibHac.Ns; using Ryujinx.Audio; +using Ryujinx.Audio.Backends.Dummy; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SoundIo; +using Ryujinx.Audio.Integration; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; using Ryujinx.Common.System; @@ -281,14 +285,14 @@ namespace Ryujinx.Ui { _virtualFileSystem.Reload(); - IRenderer renderer = new Renderer(); - IAalOutput audioEngine = new DummyAudioOut(); + IRenderer renderer = new Renderer(); + IHardwareDeviceDriver deviceDriver = new DummyHardwareDeviceDriver(); if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.SoundIo) { - if (SoundIoAudioOut.IsSupported) + if (SoundIoHardwareDeviceDriver.IsSupported) { - audioEngine = new SoundIoAudioOut(); + deviceDriver = new SoundIoHardwareDeviceDriver(); } else { @@ -297,22 +301,22 @@ namespace Ryujinx.Ui } else if (ConfigurationState.Instance.System.AudioBackend.Value == AudioBackend.OpenAl) { - if (OpenALAudioOut.IsSupported) + if (OpenALHardwareDeviceDriver.IsSupported) { - audioEngine = new OpenALAudioOut(); + deviceDriver = new OpenALHardwareDeviceDriver(); } else { Logger.Warning?.Print(LogClass.Audio, "OpenAL is not supported, trying to fall back to SoundIO."); - if (SoundIoAudioOut.IsSupported) + if (SoundIoHardwareDeviceDriver.IsSupported) { Logger.Warning?.Print(LogClass.Audio, "Found SoundIO, changing configuration."); ConfigurationState.Instance.System.AudioBackend.Value = AudioBackend.SoundIo; SaveConfig(); - audioEngine = new SoundIoAudioOut(); + deviceDriver = new SoundIoHardwareDeviceDriver(); } else { @@ -321,7 +325,7 @@ namespace Ryujinx.Ui } } - _emulationContext = new HLE.Switch(_virtualFileSystem, _contentManager, _userChannelPersistence, renderer, audioEngine) + _emulationContext = new HLE.Switch(_virtualFileSystem, _contentManager, _userChannelPersistence, renderer, deviceDriver) { UiHandler = _uiHandler }; diff --git a/Ryujinx/Ui/Windows/SettingsWindow.cs b/Ryujinx/Ui/Windows/SettingsWindow.cs index 0938783f..3bd828a4 100644 --- a/Ryujinx/Ui/Windows/SettingsWindow.cs +++ b/Ryujinx/Ui/Windows/SettingsWindow.cs @@ -1,5 +1,7 @@ using Gtk; using Ryujinx.Audio; +using Ryujinx.Audio.Backends.OpenAL; +using Ryujinx.Audio.Backends.SoundIo; using Ryujinx.Common.Configuration; using Ryujinx.Common.Configuration.Hid; using Ryujinx.Configuration; @@ -324,8 +326,8 @@ namespace Ryujinx.Ui.Windows Task.Run(() => { - openAlIsSupported = OpenALAudioOut.IsSupported; - soundIoIsSupported = SoundIoAudioOut.IsSupported; + openAlIsSupported = OpenALHardwareDeviceDriver.IsSupported; + soundIoIsSupported = SoundIoHardwareDeviceDriver.IsSupported; }); // This function runs whenever the dropdown is opened