aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer
diff options
context:
space:
mode:
authorTSR Berry <20988865+TSRBerry@users.noreply.github.com>2023-04-08 01:22:00 +0200
committerMary <thog@protonmail.com>2023-04-27 23:51:14 +0200
commitcee712105850ac3385cd0091a923438167433f9f (patch)
tree4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer
parentcd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff)
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs172
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs320
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs25
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs122
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs217
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs18
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs22
7 files changed, 896 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
new file mode 100644
index 00000000..724a1e9e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
@@ -0,0 +1,172 @@
+using Ryujinx.Audio.Renderer.Device;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.HLE.HOS.Kernel;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
+{
+ class AudioDevice : IAudioDevice
+ {
+ private VirtualDeviceSession[] _sessions;
+ private ulong _appletResourceId;
+ private int _revision;
+ private bool _isUsbDeviceSupported;
+
+ private VirtualDeviceSessionRegistry _registry;
+ private KEvent _systemEvent;
+
+ public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision)
+ {
+ _registry = registry;
+ _appletResourceId = appletResourceId;
+ _revision = revision;
+
+ BehaviourContext behaviourContext = new BehaviourContext();
+ behaviourContext.SetUserRevision(revision);
+
+ _isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
+ _sessions = _registry.GetSessionByAppletResourceId(appletResourceId);
+
+ // TODO: support the 3 different events correctly when we will have hot plugable audio devices.
+ _systemEvent = new KEvent(context);
+ _systemEvent.ReadableEvent.Signal();
+ }
+
+ private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
+ {
+ result = null;
+
+ foreach (VirtualDeviceSession session in _sessions)
+ {
+ if (session.Device.Name.Equals(name))
+ {
+ if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
+ {
+ return false;
+ }
+
+ result = session;
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public string GetActiveAudioDeviceName()
+ {
+ VirtualDevice device = _registry.ActiveDevice;
+
+ if (!_isUsbDeviceSupported && device.IsUsbDevice())
+ {
+ device = _registry.DefaultDevice;
+ }
+
+ return device.Name;
+ }
+
+ public uint GetActiveChannelCount()
+ {
+ VirtualDevice device = _registry.ActiveDevice;
+
+ if (!_isUsbDeviceSupported && device.IsUsbDevice())
+ {
+ device = _registry.DefaultDevice;
+ }
+
+ return device.ChannelCount;
+ }
+
+ public ResultCode GetAudioDeviceOutputVolume(string name, out float volume)
+ {
+ if (TryGetDeviceByName(out VirtualDeviceSession result, name))
+ {
+ volume = result.Volume;
+ }
+ else
+ {
+ volume = 0.0f;
+ }
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode SetAudioDeviceOutputVolume(string name, float volume)
+ {
+ if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
+ {
+ if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
+ {
+ result = _sessions[0];
+ }
+
+ result.Volume = volume;
+ }
+
+ return ResultCode.Success;
+ }
+
+ public string GetActiveAudioOutputDeviceName()
+ {
+ return _registry.ActiveDevice.GetOutputDeviceName();
+ }
+
+ public string[] ListAudioDeviceName()
+ {
+ int deviceCount = _sessions.Length;
+
+ if (!_isUsbDeviceSupported)
+ {
+ deviceCount--;
+ }
+
+ string[] result = new string[deviceCount];
+
+ int i = 0;
+
+ foreach (VirtualDeviceSession session in _sessions)
+ {
+ if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
+ {
+ continue;
+ }
+
+ result[i] = session.Device.Name;
+
+ i++;
+ }
+
+ return result;
+ }
+
+ public string[] ListAudioOutputDeviceName()
+ {
+ int deviceCount = _sessions.Length;
+
+ string[] result = new string[deviceCount];
+
+ for (int i = 0; i < deviceCount; i++)
+ {
+ result[i] = _sessions[i].Device.GetOutputDeviceName();
+ }
+
+ return result;
+ }
+
+ public KEvent QueryAudioDeviceInputEvent()
+ {
+ return _systemEvent;
+ }
+
+ public KEvent QueryAudioDeviceOutputEvent()
+ {
+ return _systemEvent;
+ }
+
+ public KEvent QueryAudioDeviceSystemEvent()
+ {
+ return _systemEvent;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
new file mode 100644
index 00000000..e7a75121
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
@@ -0,0 +1,320 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Text;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
+{
+ class AudioDeviceServer : IpcService
+ {
+ private const int AudioDeviceNameSize = 0x100;
+
+ private IAudioDevice _impl;
+
+ public AudioDeviceServer(IAudioDevice impl)
+ {
+ _impl = impl;
+ }
+
+ [CommandCmif(0)]
+ // ListAudioDeviceName() -> (u32, buffer<bytes, 6>)
+ public ResultCode ListAudioDeviceName(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioDeviceName();
+
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ ulong size = context.Request.ReceiveBuff[0].Size;
+
+ ulong basePosition = position;
+
+ int count = 0;
+
+ foreach (string name in deviceNames)
+ {
+ byte[] buffer = Encoding.ASCII.GetBytes(name);
+
+ if ((position - basePosition) + (ulong)buffer.Length > size)
+ {
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
+
+ position += AudioDeviceNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // SetAudioDeviceOutputVolume(f32 volume, buffer<bytes, 5> name)
+ public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context)
+ {
+ float volume = context.RequestData.ReadSingle();
+
+ ulong position = context.Request.SendBuff[0].Position;
+ ulong size = context.Request.SendBuff[0].Size;
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
+
+ return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
+ }
+
+ [CommandCmif(2)]
+ // GetAudioDeviceOutputVolume(buffer<bytes, 5> name) -> f32 volume
+ public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context)
+ {
+ ulong position = context.Request.SendBuff[0].Position;
+ ulong size = context.Request.SendBuff[0].Size;
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
+
+ ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(volume);
+ }
+
+ return result;
+ }
+
+ [CommandCmif(3)]
+ // GetActiveAudioDeviceName() -> buffer<bytes, 6>
+ public ResultCode GetActiveAudioDeviceName(ServiceCtx context)
+ {
+ string name = _impl.GetActiveAudioDeviceName();
+
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ ulong size = context.Request.ReceiveBuff[0].Size;
+
+ byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
+
+ if ((ulong)deviceNameBuffer.Length <= size)
+ {
+ context.Memory.Write(position, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(4)]
+ // QueryAudioDeviceSystemEvent() -> handle<copy, event>
+ public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context)
+ {
+ KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent();
+
+ if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5)]
+ // GetActiveChannelCount() -> u32
+ public ResultCode GetActiveChannelCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetActiveChannelCount());
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(6)] // 3.0.0+
+ // ListAudioDeviceNameAuto() -> (u32, buffer<bytes, 0x22>)
+ public ResultCode ListAudioDeviceNameAuto(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioDeviceName();
+
+ (ulong position, ulong size) = context.Request.GetBufferType0x22();
+
+ ulong basePosition = position;
+
+ int count = 0;
+
+ foreach (string name in deviceNames)
+ {
+ byte[] buffer = Encoding.ASCII.GetBytes(name);
+
+ if ((position - basePosition) + (ulong)buffer.Length > size)
+ {
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
+
+ position += AudioDeviceNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(7)] // 3.0.0+
+ // SetAudioDeviceOutputVolumeAuto(f32 volume, buffer<bytes, 0x21> name)
+ public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context)
+ {
+ float volume = context.RequestData.ReadSingle();
+
+ (ulong position, ulong size) = context.Request.GetBufferType0x21();
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
+
+ return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
+ }
+
+ [CommandCmif(8)] // 3.0.0+
+ // GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21> name) -> f32
+ public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context)
+ {
+ (ulong position, ulong size) = context.Request.GetBufferType0x21();
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
+
+ ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(volume);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10)] // 3.0.0+
+ // GetActiveAudioDeviceNameAuto() -> buffer<bytes, 0x22>
+ public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context)
+ {
+ string name = _impl.GetActiveAudioDeviceName();
+
+ (ulong position, ulong size) = context.Request.GetBufferType0x22();
+
+ byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0');
+
+ if ((ulong)deviceNameBuffer.Length <= size)
+ {
+ context.Memory.Write(position, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(11)] // 3.0.0+
+ // QueryAudioDeviceInputEvent() -> handle<copy, event>
+ public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context)
+ {
+ KEvent deviceInputEvent = _impl.QueryAudioDeviceInputEvent();
+
+ if (context.Process.HandleTable.GenerateHandle(deviceInputEvent.ReadableEvent, out int handle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(12)] // 3.0.0+
+ // QueryAudioDeviceOutputEvent() -> handle<copy, event>
+ public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context)
+ {
+ KEvent deviceOutputEvent = _impl.QueryAudioDeviceOutputEvent();
+
+ if (context.Process.HandleTable.GenerateHandle(deviceOutputEvent.ReadableEvent, out int handle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(13)] // 13.0.0+
+ // GetActiveAudioOutputDeviceName() -> buffer<bytes, 6>
+ public ResultCode GetActiveAudioOutputDeviceName(ServiceCtx context)
+ {
+ string name = _impl.GetActiveAudioOutputDeviceName();
+
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ ulong size = context.Request.ReceiveBuff[0].Size;
+
+ byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
+
+ if ((ulong)deviceNameBuffer.Length <= size)
+ {
+ context.Memory.Write(position, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+ }
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(14)] // 13.0.0+
+ // ListAudioOutputDeviceName() -> (u32, buffer<bytes, 6>)
+ public ResultCode ListAudioOutputDeviceName(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioOutputDeviceName();
+
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ ulong size = context.Request.ReceiveBuff[0].Size;
+
+ ulong basePosition = position;
+
+ int count = 0;
+
+ foreach (string name in deviceNames)
+ {
+ byte[] buffer = Encoding.ASCII.GetBytes(name);
+
+ if ((position - basePosition) + (ulong)buffer.Length > size)
+ {
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
+
+ position += AudioDeviceNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs
new file mode 100644
index 00000000..55bf29ae
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Audio.Integration;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
+{
+ class AudioKernelEvent : IWritableEvent
+ {
+ public KEvent Event { get; }
+
+ public AudioKernelEvent(KEvent evnt)
+ {
+ Event = evnt;
+ }
+
+ public void Clear()
+ {
+ Event.WritableEvent.Clear();
+ }
+
+ public void Signal()
+ {
+ Event.WritableEvent.Signal();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
new file mode 100644
index 00000000..5b682bf8
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
@@ -0,0 +1,122 @@
+using Ryujinx.Audio.Integration;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
+{
+ class AudioRenderer : IAudioRenderer
+ {
+ private AudioRenderSystem _impl;
+
+ public AudioRenderer(AudioRenderSystem impl)
+ {
+ _impl = impl;
+ }
+
+ public ResultCode ExecuteAudioRendererRendering()
+ {
+ return (ResultCode)_impl.ExecuteAudioRendererRendering();
+ }
+
+ public uint GetMixBufferCount()
+ {
+ return _impl.GetMixBufferCount();
+ }
+
+ public uint GetRenderingTimeLimit()
+ {
+ return _impl.GetRenderingTimeLimit();
+ }
+
+ public uint GetSampleCount()
+ {
+ return _impl.GetSampleCount();
+ }
+
+ public uint GetSampleRate()
+ {
+ return _impl.GetSampleRate();
+ }
+
+ public int GetState()
+ {
+ if (_impl.IsActive())
+ {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ public ResultCode QuerySystemEvent(out KEvent systemEvent)
+ {
+ ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent);
+
+ if (resultCode == ResultCode.Success)
+ {
+ if (outEvent is AudioKernelEvent)
+ {
+ systemEvent = ((AudioKernelEvent)outEvent).Event;
+ }
+ else
+ {
+ throw new NotImplementedException();
+ }
+ }
+ else
+ {
+ systemEvent = null;
+ }
+
+ return resultCode;
+ }
+
+ public ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
+ {
+ return (ResultCode)_impl.Update(output, performanceOutput, input);
+ }
+
+ public void SetRenderingTimeLimit(uint percent)
+ {
+ _impl.SetRenderingTimeLimitPercent(percent);
+ }
+
+ public ResultCode Start()
+ {
+ _impl.Start();
+
+ return ResultCode.Success;
+ }
+
+ public ResultCode Stop()
+ {
+ _impl.Stop();
+
+ return ResultCode.Success;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _impl.Dispose();
+ }
+ }
+
+ public void SetVoiceDropParameter(float voiceDropParameter)
+ {
+ _impl.SetVoiceDropParameter(voiceDropParameter);
+ }
+
+ public float GetVoiceDropParameter()
+ {
+ return _impl.GetVoiceDropParameter();
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
new file mode 100644
index 00000000..a137c413
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
@@ -0,0 +1,217 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using System;
+using System.Buffers;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
+{
+ class AudioRendererServer : DisposableIpcService
+ {
+ private IAudioRenderer _impl;
+
+ public AudioRendererServer(IAudioRenderer impl)
+ {
+ _impl = impl;
+ }
+
+ [CommandCmif(0)]
+ // GetSampleRate() -> u32
+ public ResultCode GetSampleRate(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetSampleRate());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // GetSampleCount() -> u32
+ public ResultCode GetSampleCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetSampleCount());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)]
+ // GetMixBufferCount() -> u32
+ public ResultCode GetMixBufferCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetMixBufferCount());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)]
+ // GetState() -> u32
+ public ResultCode GetState(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetState());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(4)]
+ // RequestUpdate(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5> input)
+ // -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> performanceOutput)
+ public ResultCode RequestUpdate(ServiceCtx context)
+ {
+ ulong inputPosition = context.Request.SendBuff[0].Position;
+ ulong inputSize = context.Request.SendBuff[0].Size;
+
+ ulong outputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong outputSize = context.Request.ReceiveBuff[0].Size;
+
+ ulong performanceOutputPosition = context.Request.ReceiveBuff[1].Position;
+ ulong performanceOutputSize = context.Request.ReceiveBuff[1].Size;
+
+ ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
+
+ using (IMemoryOwner<byte> outputOwner = ByteMemoryPool.Shared.RentCleared(outputSize))
+ using (IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Shared.RentCleared(performanceOutputSize))
+ {
+ Memory<byte> output = outputOwner.Memory;
+ Memory<byte> performanceOutput = performanceOutputOwner.Memory;
+
+ using MemoryHandle outputHandle = output.Pin();
+ using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
+
+ ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
+
+ if (result == ResultCode.Success)
+ {
+ context.Memory.Write(outputPosition, output.Span);
+ context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}");
+ }
+
+ return result;
+ }
+ }
+
+ [CommandCmif(5)]
+ // Start()
+ public ResultCode Start(ServiceCtx context)
+ {
+ return _impl.Start();
+ }
+
+ [CommandCmif(6)]
+ // Stop()
+ public ResultCode Stop(ServiceCtx context)
+ {
+ return _impl.Stop();
+ }
+
+ [CommandCmif(7)]
+ // QuerySystemEvent() -> handle<copy, event>
+ public ResultCode QuerySystemEvent(ServiceCtx context)
+ {
+ ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent);
+
+ if (result == ResultCode.Success)
+ {
+ if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+ }
+
+ return result;
+ }
+
+ [CommandCmif(8)]
+ // SetAudioRendererRenderingTimeLimit(u32 limit)
+ public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context)
+ {
+ uint limit = context.RequestData.ReadUInt32();
+
+ _impl.SetRenderingTimeLimit(limit);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(9)]
+ // GetAudioRendererRenderingTimeLimit() -> u32 limit
+ public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context)
+ {
+ uint limit = _impl.GetRenderingTimeLimit();
+
+ context.ResponseData.Write(limit);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10)] // 3.0.0+
+ // RequestUpdateAuto(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x21> input)
+ // -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> performanceOutput)
+ public ResultCode RequestUpdateAuto(ServiceCtx context)
+ {
+ (ulong inputPosition, ulong inputSize) = context.Request.GetBufferType0x21();
+ (ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(0);
+ (ulong performanceOutputPosition, ulong performanceOutputSize) = context.Request.GetBufferType0x22(1);
+
+ ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
+
+ Memory<byte> output = new byte[outputSize];
+ Memory<byte> performanceOutput = new byte[performanceOutputSize];
+
+ using MemoryHandle outputHandle = output.Pin();
+ using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
+
+ ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
+
+ if (result == ResultCode.Success)
+ {
+ context.Memory.Write(outputPosition, output.Span);
+ context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
+ }
+
+ return result;
+ }
+
+ [CommandCmif(11)] // 3.0.0+
+ // ExecuteAudioRendererRendering()
+ public ResultCode ExecuteAudioRendererRendering(ServiceCtx context)
+ {
+ return _impl.ExecuteAudioRendererRendering();
+ }
+
+ [CommandCmif(12)] // 15.0.0+
+ // SetVoiceDropParameter(f32 voiceDropParameter)
+ public ResultCode SetVoiceDropParameter(ServiceCtx context)
+ {
+ float voiceDropParameter = context.RequestData.ReadSingle();
+
+ _impl.SetVoiceDropParameter(voiceDropParameter);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(13)] // 15.0.0+
+ // GetVoiceDropParameter() -> f32 voiceDropParameter
+ public ResultCode GetVoiceDropParameter(ServiceCtx context)
+ {
+ float voiceDropParameter = _impl.GetVoiceDropParameter();
+
+ context.ResponseData.Write(voiceDropParameter);
+
+ return ResultCode.Success;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ if (isDisposing)
+ {
+ _impl.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
new file mode 100644
index 00000000..1918a977
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
@@ -0,0 +1,18 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
+{
+ interface IAudioDevice
+ {
+ string[] ListAudioDeviceName();
+ ResultCode SetAudioDeviceOutputVolume(string name, float volume);
+ ResultCode GetAudioDeviceOutputVolume(string name, out float volume);
+ string GetActiveAudioDeviceName();
+ KEvent QueryAudioDeviceSystemEvent();
+ uint GetActiveChannelCount();
+ KEvent QueryAudioDeviceInputEvent();
+ KEvent QueryAudioDeviceOutputEvent();
+ string GetActiveAudioOutputDeviceName();
+ string[] ListAudioOutputDeviceName();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
new file mode 100644
index 00000000..404bf4c1
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
@@ -0,0 +1,22 @@
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
+{
+ interface IAudioRenderer : IDisposable
+ {
+ uint GetSampleRate();
+ uint GetSampleCount();
+ uint GetMixBufferCount();
+ int GetState();
+ ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input);
+ ResultCode Start();
+ ResultCode Stop();
+ ResultCode QuerySystemEvent(out KEvent systemEvent);
+ void SetRenderingTimeLimit(uint percent);
+ uint GetRenderingTimeLimit();
+ ResultCode ExecuteAudioRendererRendering();
+ void SetVoiceDropParameter(float voiceDropParameter);
+ float GetVoiceDropParameter();
+ }
+}