aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.HLE/HOS/Services/Audio/AudioRenderer')
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs98
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs158
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs286
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs25
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs112
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs188
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs17
-rw-r--r--Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs20
8 files changed, 904 insertions, 0 deletions
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs
new file mode 100644
index 00000000..fdc23604
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs
@@ -0,0 +1,98 @@
+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<long> _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<long>();
+
+ _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<short> 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/AudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
new file mode 100644
index 00000000..8ba10946
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs
@@ -0,0 +1,158 @@
+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 ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume)
+ {
+ if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
+ {
+ systemMasterVolume = result.Device.MasterVolume;
+ }
+ else
+ {
+ systemMasterVolume = 0.0f;
+ }
+
+ return ResultCode.Success;
+ }
+
+ public string[] ListAudioDeviceName()
+ {
+ int deviceCount = _sessions.Length;
+
+ if (!_isUsbDeviceSupported)
+ {
+ deviceCount--;
+ }
+
+ string[] result = new string[deviceCount];
+
+ for (int i = 0; i < deviceCount; i++)
+ {
+ result[i] = _sessions[i].Device.Name;
+ }
+
+ return result;
+ }
+
+ public KEvent QueryAudioDeviceInputEvent()
+ {
+ return _systemEvent;
+ }
+
+ public KEvent QueryAudioDeviceOutputEvent()
+ {
+ return _systemEvent;
+ }
+
+ public KEvent QueryAudioDeviceSystemEvent()
+ {
+ return _systemEvent;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
new file mode 100644
index 00000000..1cf6741e
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs
@@ -0,0 +1,286 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+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;
+ }
+
+ [Command(0)]
+ // ListAudioDeviceName() -> (u32, buffer<bytes, 6>)
+ public ResultCode ListAudioDeviceName(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioDeviceName();
+
+ 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, AudioDeviceNameSize - buffer.Length);
+
+ position += AudioDeviceNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [Command(1)]
+ // SetAudioDeviceOutputVolume(f32 volume, buffer<bytes, 5> name)
+ public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context)
+ {
+ float volume = context.RequestData.ReadSingle();
+
+ long position = context.Request.SendBuff[0].Position;
+ long size = context.Request.SendBuff[0].Size;
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
+
+ return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
+ }
+
+ [Command(2)]
+ // GetAudioDeviceOutputVolume(buffer<bytes, 5> name) -> f32 volume
+ public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context)
+ {
+ long position = context.Request.SendBuff[0].Position;
+ long size = context.Request.SendBuff[0].Size;
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
+
+ ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(volume);
+ }
+
+ return result;
+ }
+
+ [Command(3)]
+ // GetActiveAudioDeviceName() -> buffer<bytes, 6>
+ public ResultCode GetActiveAudioDeviceName(ServiceCtx context)
+ {
+ string name = _impl.GetActiveAudioDeviceName();
+
+ long position = context.Request.ReceiveBuff[0].Position;
+ long size = context.Request.ReceiveBuff[0].Size;
+
+ byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
+
+ if ((ulong)deviceNameBuffer.Length <= (ulong)size)
+ {
+ context.Memory.Write((ulong)position, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(4)]
+ // QueryAudioDeviceSystemEvent() -> handle<copy, event>
+ public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context)
+ {
+ KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent();
+
+ if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(5)]
+ // GetActiveChannelCount() -> u32
+ public ResultCode GetActiveChannelCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetActiveChannelCount());
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(6)] // 3.0.0+
+ // ListAudioDeviceNameAuto() -> (u32, buffer<bytes, 0x22>)
+ public ResultCode ListAudioDeviceNameAuto(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioDeviceName();
+
+ (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, AudioDeviceNameSize - buffer.Length);
+
+ position += AudioDeviceNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [Command(7)] // 3.0.0+
+ // SetAudioDeviceOutputVolumeAuto(f32 volume, buffer<bytes, 0x21> name)
+ public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context)
+ {
+ float volume = context.RequestData.ReadSingle();
+
+ (long position, long size) = context.Request.GetBufferType0x21();
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
+
+ return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
+ }
+
+ [Command(8)] // 3.0.0+
+ // GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21> name) -> f32
+ public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context)
+ {
+ (long position, long size) = context.Request.GetBufferType0x21();
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
+
+ ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(volume);
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(10)] // 3.0.0+
+ // GetActiveAudioDeviceNameAuto() -> buffer<bytes, 0x22>
+ public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context)
+ {
+ string name = _impl.GetActiveAudioDeviceName();
+
+ (long position, long size) = context.Request.GetBufferType0x22();
+
+ byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0');
+
+ if ((ulong)deviceNameBuffer.Length <= (ulong)size)
+ {
+ context.Memory.Write((ulong)position, deviceNameBuffer);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+ }
+
+ return ResultCode.Success;
+ }
+
+ [Command(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) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(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) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ Logger.Stub?.PrintStub(LogClass.ServiceAudio);
+
+ return ResultCode.Success;
+ }
+
+ [Command(13)]
+ // GetAudioSystemMasterVolumeSetting(buffer<bytes, 5> name) -> f32
+ public ResultCode GetAudioSystemMasterVolumeSetting(ServiceCtx context)
+ {
+ long position = context.Request.SendBuff[0].Position;
+ long size = context.Request.SendBuff[0].Size;
+
+ string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, size);
+
+ ResultCode result = _impl.GetAudioSystemMasterVolumeSetting(deviceName, out float systemMasterVolume);
+
+ if (result == ResultCode.Success)
+ {
+ context.ResponseData.Write(systemMasterVolume);
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs
new file mode 100644
index 00000000..1a132b91
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs
@@ -0,0 +1,25 @@
+using Ryujinx.Audio.Renderer.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/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
new file mode 100644
index 00000000..702648dd
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs
@@ -0,0 +1,112 @@
+using Ryujinx.Audio.Renderer.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()
+ {
+ throw new NotImplementedException();
+ }
+
+ 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();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
new file mode 100644
index 00000000..5ff5ddb3
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs
@@ -0,0 +1,188 @@
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Common;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using System;
+using System.Buffers;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
+{
+ class AudioRendererServer : IpcService, IDisposable
+ {
+ private IAudioRenderer _impl;
+
+ public AudioRendererServer(IAudioRenderer impl)
+ {
+ _impl = impl;
+ }
+
+ [Command(0)]
+ // GetSampleRate() -> u32
+ public ResultCode GetSampleRate(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetSampleRate());
+
+ return ResultCode.Success;
+ }
+
+ [Command(1)]
+ // GetSampleCount() -> u32
+ public ResultCode GetSampleCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetSampleCount());
+
+ return ResultCode.Success;
+ }
+
+ [Command(2)]
+ // GetMixBufferCount() -> u32
+ public ResultCode GetMixBufferCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetMixBufferCount());
+
+ return ResultCode.Success;
+ }
+
+ [Command(3)]
+ // GetState() -> u32
+ public ResultCode GetState(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetState());
+
+ return ResultCode.Success;
+ }
+
+ [Command(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)
+ {
+ long inputPosition = context.Request.SendBuff[0].Position;
+ long inputSize = context.Request.SendBuff[0].Size;
+
+ long outputPosition = context.Request.ReceiveBuff[0].Position;
+ long outputSize = context.Request.ReceiveBuff[0].Size;
+
+ long performanceOutputPosition = context.Request.ReceiveBuff[1].Position;
+ long performanceOutputSize = context.Request.ReceiveBuff[1].Size;
+
+ ReadOnlyMemory<byte> input = context.Memory.GetSpan((ulong)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((ulong)outputPosition, output.Span);
+ context.Memory.Write((ulong)performanceOutputPosition, performanceOutput.Span);
+ }
+ else
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{result}");
+ }
+
+ return result;
+ }
+
+ [Command(5)]
+ // Start()
+ public ResultCode Start(ServiceCtx context)
+ {
+ return _impl.Start();
+ }
+
+ [Command(6)]
+ // Stop()
+ public ResultCode Stop(ServiceCtx context)
+ {
+ return _impl.Stop();
+ }
+
+ [Command(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) != KernelResult.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+ }
+
+ return result;
+ }
+
+ [Command(8)]
+ // SetAudioRendererRenderingTimeLimit(u32 limit)
+ public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context)
+ {
+ uint limit = context.RequestData.ReadUInt32();
+
+ _impl.SetRenderingTimeLimit(limit);
+
+ return ResultCode.Success;
+ }
+
+ [Command(9)]
+ // GetAudioRendererRenderingTimeLimit() -> u32 limit
+ public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context)
+ {
+ uint limit = _impl.GetRenderingTimeLimit();
+
+ context.ResponseData.Write(limit);
+
+ return ResultCode.Success;
+ }
+
+ [Command(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)
+ {
+ (long inputPosition, long inputSize) = context.Request.GetBufferType0x21();
+ (long outputPosition, long outputSize) = context.Request.GetBufferType0x22(0);
+ (long performanceOutputPosition, long performanceOutputSize) = context.Request.GetBufferType0x22(1);
+
+ ReadOnlyMemory<byte> input = context.Memory.GetSpan((ulong)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((ulong)outputPosition, output.Span);
+ context.Memory.Write((ulong)performanceOutputPosition, performanceOutput.Span);
+ }
+
+ return result;
+ }
+
+ public void Dispose()
+ {
+ Dispose(true);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _impl.Dispose();
+ }
+ }
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
new file mode 100644
index 00000000..42ea727f
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs
@@ -0,0 +1,17 @@
+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();
+ ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume);
+ }
+}
diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
new file mode 100644
index 00000000..a59c94e9
--- /dev/null
+++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs
@@ -0,0 +1,20 @@
+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();
+ }
+}