aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.HLE/HOS/Services/Audio
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Audio')
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs108
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs204
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs34
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs235
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs108
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs185
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs33
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs41
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs162
-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
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs67
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs116
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs27
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs92
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs116
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs28
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs12
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs8
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs205
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs21
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs11
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs15
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs19
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs23
-rw-r--r--src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs15
45 files changed, 2944 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs
new file mode 100644
index 00000000..ee85ded9
--- /dev/null
+++ b/src/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<ulong> 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/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
new file mode 100644
index 00000000..a80b9402
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
@@ -0,0 +1,204 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
+{
+ class AudioInServer : DisposableIpcService
+ {
+ private IAudioIn _impl;
+
+ public AudioInServer(IAudioIn impl)
+ {
+ _impl = impl;
+ }
+
+ [CommandCmif(0)]
+ // GetAudioInState() -> u32 state
+ public ResultCode GetAudioInState(ServiceCtx context)
+ {
+ context.ResponseData.Write((uint)_impl.GetState());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // Start()
+ public ResultCode Start(ServiceCtx context)
+ {
+ return _impl.Start();
+ }
+
+ [CommandCmif(2)]
+ // Stop()
+ public ResultCode StopAudioIn(ServiceCtx context)
+ {
+ return _impl.Stop();
+ }
+
+ [CommandCmif(3)]
+ // AppendAudioInBuffer(u64 tag, buffer<nn::audio::AudioInBuffer, 5>)
+ public ResultCode AppendAudioInBuffer(ServiceCtx context)
+ {
+ ulong position = context.Request.SendBuff[0].Position;
+
+ ulong bufferTag = context.RequestData.ReadUInt64();
+
+ AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
+
+ return _impl.AppendBuffer(bufferTag, ref data);
+ }
+
+ [CommandCmif(4)]
+ // RegisterBufferEvent() -> handle<copy>
+ public ResultCode RegisterBufferEvent(ServiceCtx context)
+ {
+ KEvent bufferEvent = _impl.RegisterBufferEvent();
+
+ if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5)]
+ // GetReleasedAudioInBuffers() -> (u32 count, buffer<u64, 6> tags)
+ public ResultCode GetReleasedAudioInBuffers(ServiceCtx context)
+ {
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ ulong size = context.Request.ReceiveBuff[0].Size;
+
+ using (WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size))
+ {
+ ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
+
+ context.ResponseData.Write(releasedCount);
+
+ return result;
+ }
+ }
+
+ [CommandCmif(6)]
+ // ContainsAudioInBuffer(u64 tag) -> b8
+ public ResultCode ContainsAudioInBuffer(ServiceCtx context)
+ {
+ ulong bufferTag = context.RequestData.ReadUInt64();
+
+ context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(7)] // 3.0.0+
+ // AppendUacInBuffer(u64 tag, handle<copy, unknown>, buffer<nn::audio::AudioInBuffer, 5>)
+ public ResultCode AppendUacInBuffer(ServiceCtx context)
+ {
+ ulong position = context.Request.SendBuff[0].Position;
+
+ ulong bufferTag = context.RequestData.ReadUInt64();
+ uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
+
+ AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
+
+ return _impl.AppendUacBuffer(bufferTag, ref data, handle);
+ }
+
+ [CommandCmif(8)] // 3.0.0+
+ // AppendAudioInBufferAuto(u64 tag, buffer<nn::audio::AudioInBuffer, 0x21>)
+ public ResultCode AppendAudioInBufferAuto(ServiceCtx context)
+ {
+ (ulong position, _) = context.Request.GetBufferType0x21();
+
+ ulong bufferTag = context.RequestData.ReadUInt64();
+
+ AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
+
+ return _impl.AppendBuffer(bufferTag, ref data);
+ }
+
+ [CommandCmif(9)] // 3.0.0+
+ // GetReleasedAudioInBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
+ public ResultCode GetReleasedAudioInBuffersAuto(ServiceCtx context)
+ {
+ (ulong position, ulong size) = context.Request.GetBufferType0x22();
+
+ using (WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size))
+ {
+ ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
+
+ context.ResponseData.Write(releasedCount);
+
+ return result;
+ }
+ }
+
+ [CommandCmif(10)] // 3.0.0+
+ // AppendUacInBufferAuto(u64 tag, handle<copy, event>, buffer<nn::audio::AudioInBuffer, 0x21>)
+ public ResultCode AppendUacInBufferAuto(ServiceCtx context)
+ {
+ (ulong position, _) = context.Request.GetBufferType0x21();
+
+ ulong bufferTag = context.RequestData.ReadUInt64();
+ uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
+
+ AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
+
+ return _impl.AppendUacBuffer(bufferTag, ref data, handle);
+ }
+
+ [CommandCmif(11)] // 4.0.0+
+ // GetAudioInBufferCount() -> u32
+ public ResultCode GetAudioInBufferCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetBufferCount());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(12)] // 4.0.0+
+ // SetAudioInVolume(s32)
+ public ResultCode SetAudioInVolume(ServiceCtx context)
+ {
+ float volume = context.RequestData.ReadSingle();
+
+ _impl.SetVolume(volume);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(13)] // 4.0.0+
+ // GetAudioInVolume() -> s32
+ public ResultCode GetAudioInVolume(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetVolume());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(14)] // 6.0.0+
+ // FlushAudioInBuffers() -> b8
+ public ResultCode FlushAudioInBuffers(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.FlushBuffers());
+
+ return ResultCode.Success;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ if (isDisposing)
+ {
+ _impl.Dispose();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs
new file mode 100644
index 00000000..b5073fce
--- /dev/null
+++ b/src/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<ulong> releasedBuffers, out uint releasedCount);
+
+ bool ContainsBuffer(ulong bufferTag);
+
+ uint GetBufferCount();
+
+ bool FlushBuffers();
+
+ void SetVolume(float volume);
+
+ float GetVolume();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs
new file mode 100644
index 00000000..2d342206
--- /dev/null
+++ b/src/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/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs
new file mode 100644
index 00000000..755caee5
--- /dev/null
+++ b/src/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;
+ }
+
+ [CommandCmif(0)]
+ // ListAudioIns() -> (u32, buffer<bytes, 6>)
+ public ResultCode ListAudioIns(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioIns(false);
+
+ 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)
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
+
+ position += AudioInNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // OpenAudioIn(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
+ // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
+ public ResultCode OpenAudioIn(ServiceCtx context)
+ {
+ AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
+ ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
+
+ ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
+
+ uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
+
+ string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)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(deviceNameOutputPosition, outputDeviceNameRaw);
+ MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
+
+ MakeObject(context, new AudioInServer(obj));
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(2)] // 3.0.0+
+ // ListAudioInsAuto() -> (u32, buffer<bytes, 0x22>)
+ public ResultCode ListAudioInsAuto(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioIns(false);
+
+ (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)
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
+
+ position += AudioInNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)] // 3.0.0+
+ // OpenAudioInAuto(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 0x21>)
+ // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 0x22> name)
+ public ResultCode OpenAudioInAuto(ServiceCtx context)
+ {
+ AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ (ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
+ (ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
+
+ uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
+
+ string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)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(deviceNameOutputPosition, outputDeviceNameRaw);
+ MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
+
+ MakeObject(context, new AudioInServer(obj));
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(4)] // 3.0.0+
+ // ListAudioInsAutoFiltered() -> (u32, buffer<bytes, 0x22>)
+ public ResultCode ListAudioInsAutoFiltered(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioIns(true);
+
+ (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)
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
+
+ position += AudioInNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5)] // 5.0.0+
+ // OpenAudioInProtocolSpecified(b64 protocol_specified_related, AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
+ // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> 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<AudioInputConfiguration>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
+ ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
+
+ ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
+
+ uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
+
+ string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)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(deviceNameOutputPosition, outputDeviceNameRaw);
+ MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
+
+ MakeObject(context, new AudioInServer(obj));
+ }
+
+ return resultCode;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs
new file mode 100644
index 00000000..f2588452
--- /dev/null
+++ b/src/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<ulong> 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/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
new file mode 100644
index 00000000..329e1794
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
@@ -0,0 +1,185 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.Cpu;
+using Ryujinx.HLE.HOS.Ipc;
+using Ryujinx.HLE.HOS.Kernel.Threading;
+using Ryujinx.Horizon.Common;
+using Ryujinx.Memory;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
+{
+ class AudioOutServer : DisposableIpcService
+ {
+ private IAudioOut _impl;
+
+ public AudioOutServer(IAudioOut impl)
+ {
+ _impl = impl;
+ }
+
+ [CommandCmif(0)]
+ // GetAudioOutState() -> u32 state
+ public ResultCode GetAudioOutState(ServiceCtx context)
+ {
+ context.ResponseData.Write((uint)_impl.GetState());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // Start()
+ public ResultCode Start(ServiceCtx context)
+ {
+ return _impl.Start();
+ }
+
+ [CommandCmif(2)]
+ // Stop()
+ public ResultCode Stop(ServiceCtx context)
+ {
+ return _impl.Stop();
+ }
+
+ [CommandCmif(3)]
+ // AppendAudioOutBuffer(u64 bufferTag, buffer<nn::audio::AudioOutBuffer, 5> buffer)
+ public ResultCode AppendAudioOutBuffer(ServiceCtx context)
+ {
+ ulong position = context.Request.SendBuff[0].Position;
+
+ ulong bufferTag = context.RequestData.ReadUInt64();
+
+ AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
+
+ return _impl.AppendBuffer(bufferTag, ref data);
+ }
+
+ [CommandCmif(4)]
+ // RegisterBufferEvent() -> handle<copy>
+ public ResultCode RegisterBufferEvent(ServiceCtx context)
+ {
+ KEvent bufferEvent = _impl.RegisterBufferEvent();
+
+ if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
+ {
+ throw new InvalidOperationException("Out of handles!");
+ }
+
+ context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5)]
+ // GetReleasedAudioOutBuffers() -> (u32 count, buffer<u64, 6> tags)
+ public ResultCode GetReleasedAudioOutBuffers(ServiceCtx context)
+ {
+ ulong position = context.Request.ReceiveBuff[0].Position;
+ ulong size = context.Request.ReceiveBuff[0].Size;
+
+ using (WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size))
+ {
+ ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
+
+ context.ResponseData.Write(releasedCount);
+
+ return result;
+ }
+ }
+
+ [CommandCmif(6)]
+ // ContainsAudioOutBuffer(u64 tag) -> b8
+ public ResultCode ContainsAudioOutBuffer(ServiceCtx context)
+ {
+ ulong bufferTag = context.RequestData.ReadUInt64();
+
+ context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(7)] // 3.0.0+
+ // AppendAudioOutBufferAuto(u64 tag, buffer<nn::audio::AudioOutBuffer, 0x21>)
+ public ResultCode AppendAudioOutBufferAuto(ServiceCtx context)
+ {
+ (ulong position, _) = context.Request.GetBufferType0x21();
+
+ ulong bufferTag = context.RequestData.ReadUInt64();
+
+ AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
+
+ return _impl.AppendBuffer(bufferTag, ref data);
+ }
+
+ [CommandCmif(8)] // 3.0.0+
+ // GetReleasedAudioOutBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
+ public ResultCode GetReleasedAudioOutBuffersAuto(ServiceCtx context)
+ {
+ (ulong position, ulong size) = context.Request.GetBufferType0x22();
+
+ using (WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size))
+ {
+ ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
+
+ context.ResponseData.Write(releasedCount);
+
+ return result;
+ }
+ }
+
+ [CommandCmif(9)] // 4.0.0+
+ // GetAudioOutBufferCount() -> u32
+ public ResultCode GetAudioOutBufferCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetBufferCount());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(10)] // 4.0.0+
+ // GetAudioOutPlayedSampleCount() -> u64
+ public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetPlayedSampleCount());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(11)] // 4.0.0+
+ // FlushAudioOutBuffers() -> b8
+ public ResultCode FlushAudioOutBuffers(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.FlushBuffers());
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(12)] // 6.0.0+
+ // SetAudioOutVolume(s32)
+ public ResultCode SetAudioOutVolume(ServiceCtx context)
+ {
+ float volume = context.RequestData.ReadSingle();
+
+ _impl.SetVolume(volume);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(13)] // 6.0.0+
+ // GetAudioOutVolume() -> s32
+ public ResultCode GetAudioOutVolume(ServiceCtx context)
+ {
+ context.ResponseData.Write(_impl.GetVolume());
+
+ return ResultCode.Success;
+ }
+
+ protected override void Dispose(bool isDisposing)
+ {
+ if (isDisposing)
+ {
+ _impl.Dispose();
+ }
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs
new file mode 100644
index 00000000..8533d3c5
--- /dev/null
+++ b/src/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<ulong> releasedBuffers, out uint releasedCount);
+
+ bool ContainsBuffer(ulong bufferTag);
+
+ uint GetBufferCount();
+
+ ulong GetPlayedSampleCount();
+
+ bool FlushBuffers();
+
+ void SetVolume(float volume);
+
+ float GetVolume();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs
new file mode 100644
index 00000000..7b289196
--- /dev/null
+++ b/src/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, float volume)
+ {
+ 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, volume);
+
+ if (result == ResultCode.Success)
+ {
+ obj = new AudioOut.AudioOut(outSystem, context.Device.System.KernelContext, processHandle);
+ }
+ else
+ {
+ obj = null;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs
new file mode 100644
index 00000000..7c5d8c4e
--- /dev/null
+++ b/src/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;
+ }
+
+ [CommandCmif(0)]
+ // ListAudioOuts() -> (u32, buffer<bytes, 6>)
+ public ResultCode ListAudioOuts(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioOuts();
+
+ 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)
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
+
+ position += AudioOutNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 5> name_in)
+ // -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 6> name_out)
+ public ResultCode OpenAudioOut(ServiceCtx context)
+ {
+ AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
+ ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
+
+ ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
+
+ uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
+
+ string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
+
+ ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
+
+ if (resultCode == ResultCode.Success)
+ {
+ context.ResponseData.WriteStruct(outputConfiguration);
+
+ byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
+
+ context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
+ MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
+
+ MakeObject(context, new AudioOutServer(obj));
+ }
+
+ return resultCode;
+ }
+
+ [CommandCmif(2)] // 3.0.0+
+ // ListAudioOutsAuto() -> (u32, buffer<bytes, 0x22>)
+ public ResultCode ListAudioOutsAuto(ServiceCtx context)
+ {
+ string[] deviceNames = _impl.ListAudioOuts();
+
+ (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)
+ {
+ Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
+
+ break;
+ }
+
+ context.Memory.Write(position, buffer);
+ MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
+
+ position += AudioOutNameSize;
+ count++;
+ }
+
+ context.ResponseData.Write(count);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)] // 3.0.0+
+ // OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 0x21> name_in)
+ // -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 0x22> name_out)
+ public ResultCode OpenAudioOutAuto(ServiceCtx context)
+ {
+ AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ (ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
+ (ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
+
+ uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
+
+ string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
+
+ ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
+
+ if (resultCode == ResultCode.Success)
+ {
+ context.ResponseData.WriteStruct(outputConfiguration);
+
+ byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
+
+ context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
+ MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
+
+ MakeObject(context, new AudioOutServer(obj));
+ }
+
+ return resultCode;
+ }
+ }
+}
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();
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs
new file mode 100644
index 00000000..40e71a43
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs
@@ -0,0 +1,67 @@
+using Ryujinx.Audio.Renderer.Device;
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
+
+using AudioRendererManagerImpl = Ryujinx.Audio.Renderer.Server.AudioRendererManager;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ class AudioRendererManager : IAudioRendererManager
+ {
+ private AudioRendererManagerImpl _impl;
+ private VirtualDeviceSessionRegistry _registry;
+
+ public AudioRendererManager(AudioRendererManagerImpl impl, VirtualDeviceSessionRegistry registry)
+ {
+ _impl = impl;
+ _registry = registry;
+ }
+
+ public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId)
+ {
+ outObject = new AudioDevice(_registry, context.Device.System.KernelContext, appletResourceUserId, revision);
+
+ return ResultCode.Success;
+ }
+
+ public ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
+ {
+ return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter);
+ }
+
+ public ResultCode OpenAudioRenderer(
+ ServiceCtx context,
+ out IAudioRenderer obj,
+ ref AudioRendererConfiguration parameter,
+ ulong workBufferSize,
+ ulong appletResourceUserId,
+ KTransferMemory workBufferTransferMemory,
+ uint processHandle)
+ {
+ var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
+
+ ResultCode result = (ResultCode)_impl.OpenAudioRenderer(
+ out AudioRenderSystem renderer,
+ memoryManager,
+ ref parameter,
+ appletResourceUserId,
+ workBufferTransferMemory.Address,
+ workBufferTransferMemory.Size,
+ processHandle,
+ context.Device.Configuration.AudioVolume);
+
+ if (result == ResultCode.Success)
+ {
+ obj = new AudioRenderer.AudioRenderer(renderer);
+ }
+ else
+ {
+ obj = null;
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs
new file mode 100644
index 00000000..80b54e8c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs
@@ -0,0 +1,116 @@
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.Audio.Renderer.Server;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audren:u")]
+ class AudioRendererManagerServer : IpcService
+ {
+ private const int InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24);
+
+ private IAudioRendererManager _impl;
+
+ public AudioRendererManagerServer(ServiceCtx context) : this(context, new AudioRendererManager(context.Device.System.AudioRendererManager, context.Device.System.AudioDeviceSessionRegistry)) { }
+
+ public AudioRendererManagerServer(ServiceCtx context, IAudioRendererManager impl) : base(context.Device.System.AudRenServer)
+ {
+ _impl = impl;
+ }
+
+ [CommandCmif(0)]
+ // OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal parameter, u64 workBufferSize, nn::applet::AppletResourceUserId appletResourceId, pid, handle<copy> workBuffer, handle<copy> processHandle)
+ // -> object<nn::audio::detail::IAudioRenderer>
+ public ResultCode OpenAudioRenderer(ServiceCtx context)
+ {
+ AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
+ ulong workBufferSize = context.RequestData.ReadUInt64();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0];
+ KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject<KTransferMemory>(transferMemoryHandle);
+ uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1];
+
+ ResultCode result = _impl.OpenAudioRenderer(
+ context,
+ out IAudioRenderer renderer,
+ ref parameter,
+ workBufferSize,
+ appletResourceUserId,
+ workBufferTransferMemory,
+ processHandle);
+
+ if (result == ResultCode.Success)
+ {
+ MakeObject(context, new AudioRendererServer(renderer));
+ }
+
+ context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle);
+ context.Device.System.KernelContext.Syscall.CloseHandle((int)processHandle);
+
+ return result;
+ }
+
+ [CommandCmif(1)]
+ // GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal parameter) -> u64 workBufferSize
+ public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context)
+ {
+ AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
+
+ if (BehaviourContext.CheckValidRevision(parameter.Revision))
+ {
+ ulong size = _impl.GetWorkBufferSize(ref parameter);
+
+ context.ResponseData.Write(size);
+
+ Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}.");
+
+ return ResultCode.Success;
+ }
+ else
+ {
+ context.ResponseData.Write(0L);
+
+ Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Revision)} is not supported!");
+
+ return ResultCode.UnsupportedRevision;
+ }
+ }
+
+ [CommandCmif(2)]
+ // GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice>
+ public ResultCode GetAudioDeviceService(ServiceCtx context)
+ {
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, InitialRevision, appletResourceUserId);
+
+ if (result == ResultCode.Success)
+ {
+ MakeObject(context, new AudioDeviceServer(device));
+ }
+
+ return result;
+ }
+
+ [CommandCmif(4)] // 4.0.0+
+ // GetAudioDeviceServiceWithRevisionInfo(s32 revision, nn::applet::AppletResourceUserId appletResourceId) -> object<nn::audio::detail::IAudioDevice>
+ public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context)
+ {
+ int revision = context.RequestData.ReadInt32();
+ ulong appletResourceUserId = context.RequestData.ReadUInt64();
+
+ ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, revision, appletResourceUserId);
+
+ if (result == ResultCode.Success)
+ {
+ MakeObject(context, new AudioDeviceServer(device));
+ }
+
+ return result;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs
new file mode 100644
index 00000000..b77fc4b0
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs
@@ -0,0 +1,27 @@
+using Concentus.Structs;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
+{
+ class Decoder : IDecoder
+ {
+ private readonly OpusDecoder _decoder;
+
+ public int SampleRate => _decoder.SampleRate;
+ public int ChannelsCount => _decoder.NumChannels;
+
+ public Decoder(int sampleRate, int channelsCount)
+ {
+ _decoder = new OpusDecoder(sampleRate, channelsCount);
+ }
+
+ public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
+ {
+ return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
+ }
+
+ public void ResetState()
+ {
+ _decoder.ResetState();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs
new file mode 100644
index 00000000..944541cc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs
@@ -0,0 +1,92 @@
+using Concentus;
+using Concentus.Enums;
+using Concentus.Structs;
+using Ryujinx.HLE.HOS.Services.Audio.Types;
+using System;
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
+{
+ static class DecoderCommon
+ {
+ private static ResultCode GetPacketNumSamples(this IDecoder decoder, out int numSamples, byte[] packet)
+ {
+ int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
+
+ numSamples = result;
+
+ if (result == OpusError.OPUS_INVALID_PACKET)
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+ else if (result == OpusError.OPUS_BAD_ARG)
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ return ResultCode.Success;
+ }
+
+ public static ResultCode DecodeInterleaved(
+ this IDecoder decoder,
+ bool reset,
+ ReadOnlySpan<byte> input,
+ out short[] outPcmData,
+ ulong outputSize,
+ out uint outConsumed,
+ out int outSamples)
+ {
+ outPcmData = null;
+ outConsumed = 0;
+ outSamples = 0;
+
+ int streamSize = input.Length;
+
+ if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
+ int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
+ uint totalSize = header.length + (uint)headerSize;
+
+ if (totalSize > streamSize)
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ byte[] opusData = input.Slice(headerSize, (int)header.length).ToArray();
+
+ ResultCode result = decoder.GetPacketNumSamples(out int numSamples, opusData);
+
+ if (result == ResultCode.Success)
+ {
+ if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
+ {
+ return ResultCode.OpusInvalidInput;
+ }
+
+ outPcmData = new short[numSamples * decoder.ChannelsCount];
+
+ if (reset)
+ {
+ decoder.ResetState();
+ }
+
+ try
+ {
+ outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
+ outConsumed = totalSize;
+ }
+ catch (OpusException)
+ {
+ // TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
+ return ResultCode.OpusInvalidInput;
+ }
+ }
+
+ return ResultCode.Success;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs
new file mode 100644
index 00000000..9047c266
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
+{
+ interface IDecoder
+ {
+ int SampleRate { get; }
+ int ChannelsCount { get; }
+
+ int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
+ void ResetState();
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
new file mode 100644
index 00000000..e94b31ca
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs
@@ -0,0 +1,116 @@
+using Ryujinx.HLE.HOS.Services.Audio.Types;
+using System;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
+{
+ class IHardwareOpusDecoder : IpcService
+ {
+ private readonly IDecoder _decoder;
+ private readonly OpusDecoderFlags _flags;
+
+ public IHardwareOpusDecoder(int sampleRate, int channelsCount, OpusDecoderFlags flags)
+ {
+ _decoder = new Decoder(sampleRate, channelsCount);
+ _flags = flags;
+ }
+
+ public IHardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, OpusDecoderFlags flags, byte[] mapping)
+ {
+ _decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
+ _flags = flags;
+ }
+
+ [CommandCmif(0)]
+ // DecodeInterleavedOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
+ public ResultCode DecodeInterleavedOld(ServiceCtx context)
+ {
+ return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
+ }
+
+ [CommandCmif(2)]
+ // DecodeInterleavedForMultiStreamOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
+ public ResultCode DecodeInterleavedForMultiStreamOld(ServiceCtx context)
+ {
+ return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
+ }
+
+ [CommandCmif(4)] // 6.0.0+
+ // DecodeInterleavedWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
+ public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context)
+ {
+ return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
+ }
+
+ [CommandCmif(5)] // 6.0.0+
+ // DecodeInterleavedForMultiStreamWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
+ public ResultCode DecodeInterleavedForMultiStreamWithPerfOld(ServiceCtx context)
+ {
+ return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
+ }
+
+ [CommandCmif(6)] // 6.0.0+
+ // DecodeInterleavedWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
+ public ResultCode DecodeInterleavedWithPerfAndResetOld(ServiceCtx context)
+ {
+ bool reset = context.RequestData.ReadBoolean();
+
+ return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
+ }
+
+ [CommandCmif(7)] // 6.0.0+
+ // DecodeInterleavedForMultiStreamWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
+ public ResultCode DecodeInterleavedForMultiStreamWithPerfAndResetOld(ServiceCtx context)
+ {
+ bool reset = context.RequestData.ReadBoolean();
+
+ return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
+ }
+
+ [CommandCmif(8)] // 7.0.0+
+ // DecodeInterleaved(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
+ public ResultCode DecodeInterleaved(ServiceCtx context)
+ {
+ bool reset = context.RequestData.ReadBoolean();
+
+ return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
+ }
+
+ [CommandCmif(9)] // 7.0.0+
+ // DecodeInterleavedForMultiStream(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
+ public ResultCode DecodeInterleavedForMultiStream(ServiceCtx context)
+ {
+ bool reset = context.RequestData.ReadBoolean();
+
+ return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
+ }
+
+ private ResultCode DecodeInterleavedInternal(ServiceCtx context, OpusDecoderFlags flags, bool reset, bool withPerf)
+ {
+ ulong inPosition = context.Request.SendBuff[0].Position;
+ ulong inSize = context.Request.SendBuff[0].Size;
+ ulong outputPosition = context.Request.ReceiveBuff[0].Position;
+ ulong outputSize = context.Request.ReceiveBuff[0].Size;
+
+ ReadOnlySpan<byte> input = context.Memory.GetSpan(inPosition, (int)inSize);
+
+ ResultCode result = _decoder.DecodeInterleaved(reset, input, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
+
+ if (result == ResultCode.Success)
+ {
+ context.Memory.Write(outputPosition, MemoryMarshal.Cast<short, byte>(outPcmData.AsSpan()));
+
+ context.ResponseData.Write(outConsumed);
+ context.ResponseData.Write(outSamples);
+
+ if (withPerf)
+ {
+ // This is the time the DSP took to process the request, TODO: fill this.
+ context.ResponseData.Write(0UL);
+ }
+ }
+
+ return result;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs
new file mode 100644
index 00000000..23721d3b
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs
@@ -0,0 +1,28 @@
+using Concentus.Structs;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
+{
+ class MultiSampleDecoder : IDecoder
+ {
+ private readonly OpusMSDecoder _decoder;
+
+ public int SampleRate => _decoder.SampleRate;
+ public int ChannelsCount { get; }
+
+ public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
+ {
+ ChannelsCount = channelsCount;
+ _decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
+ }
+
+ public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
+ {
+ return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
+ }
+
+ public void ResetState()
+ {
+ _decoder.ResetState();
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs
new file mode 100644
index 00000000..1bd2e31d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audctl")]
+ class IAudioController : IpcService
+ {
+ public IAudioController(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs
new file mode 100644
index 00000000..9bbe5b0e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ interface IAudioInManager
+ {
+ public string[] ListAudioIns(bool filtered);
+
+ 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/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs
new file mode 100644
index 00000000..37d9a8fe
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audin:a")]
+ class IAudioInManagerForApplet : IpcService
+ {
+ public IAudioInManagerForApplet(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs
new file mode 100644
index 00000000..1a497efb
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audin:d")]
+ class IAudioInManagerForDebugger : IpcService
+ {
+ public IAudioInManagerForDebugger(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs
new file mode 100644
index 00000000..70e60d2e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs
@@ -0,0 +1,12 @@
+using Ryujinx.Audio.Common;
+using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ interface IAudioOutManager
+ {
+ public string[] ListAudioOuts();
+
+ public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs
new file mode 100644
index 00000000..4b41b0cf
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audout:a")]
+ class IAudioOutManagerForApplet : IpcService
+ {
+ public IAudioOutManagerForApplet(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs
new file mode 100644
index 00000000..41cde972
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audout:d")]
+ class IAudioOutManagerForDebugger : IpcService
+ {
+ public IAudioOutManagerForDebugger(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs
new file mode 100644
index 00000000..642e2525
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Audio.Renderer.Parameter;
+using Ryujinx.HLE.HOS.Kernel.Memory;
+using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ interface IAudioRendererManager
+ {
+ // TODO: Remove ServiceCtx argument
+ // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
+ ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId);
+
+ // TODO: Remove ServiceCtx argument
+ // BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
+ ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle);
+
+ ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter);
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs
new file mode 100644
index 00000000..ca5768cc
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audren:a")]
+ class IAudioRendererManagerForApplet : IpcService
+ {
+ public IAudioRendererManagerForApplet(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs
new file mode 100644
index 00000000..a970ae45
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audren:d")]
+ class IAudioRendererManagerForDebugger : IpcService
+ {
+ public IAudioRendererManagerForDebugger(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs
new file mode 100644
index 00000000..59e3ad09
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("auddev")] // 6.0.0+
+ class IAudioSnoopManager : IpcService
+ {
+ public IAudioSnoopManager(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs
new file mode 100644
index 00000000..01435008
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audrec:u")]
+ class IFinalOutputRecorderManager : IpcService
+ {
+ public IFinalOutputRecorderManager(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs
new file mode 100644
index 00000000..d8fd270d
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audrec:a")]
+ class IFinalOutputRecorderManagerForApplet : IpcService
+ {
+ public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs
new file mode 100644
index 00000000..a8ec51ee
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("audrec:d")]
+ class IFinalOutputRecorderManagerForDebugger : IpcService
+ {
+ public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs
new file mode 100644
index 00000000..8df8a38c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs
@@ -0,0 +1,205 @@
+using Ryujinx.Common;
+using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager;
+using Ryujinx.HLE.HOS.Services.Audio.Types;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ [Service("hwopus")]
+ class IHardwareOpusDecoderManager : IpcService
+ {
+ public IHardwareOpusDecoderManager(ServiceCtx context) { }
+
+ [CommandCmif(0)]
+ // Initialize(bytes<8, 4>, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder>
+ public ResultCode Initialize(ServiceCtx context)
+ {
+ int sampleRate = context.RequestData.ReadInt32();
+ int channelsCount = context.RequestData.ReadInt32();
+
+ MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount, OpusDecoderFlags.None));
+
+ // Close transfer memory immediately as we don't use it.
+ context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(1)]
+ // GetWorkBufferSize(bytes<8, 4>) -> u32
+ public ResultCode GetWorkBufferSize(ServiceCtx context)
+ {
+ int sampleRate = context.RequestData.ReadInt32();
+ int channelsCount = context.RequestData.ReadInt32();
+
+ int opusDecoderSize = GetOpusDecoderSize(channelsCount);
+
+ int frameSize = BitUtils.AlignUp(channelsCount * 1920 / (48000 / sampleRate), 64);
+ int totalSize = opusDecoderSize + 1536 + frameSize;
+
+ context.ResponseData.Write(totalSize);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(2)] // 3.0.0+
+ // InitializeForMultiStream(u32, handle<copy>, buffer<unknown<0x110>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
+ public ResultCode InitializeForMultiStream(ServiceCtx context)
+ {
+ ulong parametersAddress = context.Request.PtrBuff[0].Position;
+
+ OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
+
+ MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, OpusDecoderFlags.None));
+
+ // Close transfer memory immediately as we don't use it.
+ context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(3)] // 3.0.0+
+ // GetWorkBufferSizeForMultiStream(buffer<unknown<0x110>, 0x19>) -> u32
+ public ResultCode GetWorkBufferSizeForMultiStream(ServiceCtx context)
+ {
+ ulong parametersAddress = context.Request.PtrBuff[0].Position;
+
+ OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
+
+ int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
+
+ int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
+ int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * 1920 / (48000 / parameters.SampleRate), 64);
+ int totalSize = opusDecoderSize + streamSize + frameSize;
+
+ context.ResponseData.Write(totalSize);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(4)] // 12.0.0+
+ // InitializeEx(OpusParametersEx, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder>
+ public ResultCode InitializeEx(ServiceCtx context)
+ {
+ OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
+
+ // UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
+ MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, parameters.Flags));
+
+ // Close transfer memory immediately as we don't use it.
+ context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(5)] // 12.0.0+
+ // GetWorkBufferSizeEx(OpusParametersEx) -> u32
+ public ResultCode GetWorkBufferSizeEx(ServiceCtx context)
+ {
+ OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
+
+ int opusDecoderSize = GetOpusDecoderSize(parameters.ChannelsCount);
+
+ int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
+ int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
+ int totalSize = opusDecoderSize + 1536 + frameSize;
+
+ context.ResponseData.Write(totalSize);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(6)] // 12.0.0+
+ // InitializeForMultiStreamEx(u32, handle<copy>, buffer<unknown<0x118>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
+ public ResultCode InitializeForMultiStreamEx(ServiceCtx context)
+ {
+ ulong parametersAddress = context.Request.PtrBuff[0].Position;
+
+ OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
+
+ byte[] mappings = MemoryMarshal.Cast<uint, byte>(parameters.ChannelMappings.AsSpan()).ToArray();
+
+ // UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
+ MakeObject(context, new IHardwareOpusDecoder(
+ parameters.SampleRate,
+ parameters.ChannelsCount,
+ parameters.NumberOfStreams,
+ parameters.NumberOfStereoStreams,
+ parameters.Flags,
+ mappings));
+
+ // Close transfer memory immediately as we don't use it.
+ context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
+
+ return ResultCode.Success;
+ }
+
+ [CommandCmif(7)] // 12.0.0+
+ // GetWorkBufferSizeForMultiStreamEx(buffer<unknown<0x118>, 0x19>) -> u32
+ public ResultCode GetWorkBufferSizeForMultiStreamEx(ServiceCtx context)
+ {
+ ulong parametersAddress = context.Request.PtrBuff[0].Position;
+
+ OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
+
+ int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
+
+ int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
+ int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
+ int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
+ int totalSize = opusDecoderSize + streamSize + frameSize;
+
+ context.ResponseData.Write(totalSize);
+
+ return ResultCode.Success;
+ }
+
+ private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
+ {
+ if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
+ {
+ return 0;
+ }
+
+ int coupledSize = GetOpusDecoderSize(2);
+ int monoSize = GetOpusDecoderSize(1);
+
+ return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
+ Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb90c;
+ }
+
+ private static int Align4(int value)
+ {
+ return BitUtils.AlignUp(value, 4);
+ }
+
+ private static int GetOpusDecoderSize(int channelsCount)
+ {
+ const int SilkDecoderSize = 0x2160;
+
+ if (channelsCount < 1 || channelsCount > 2)
+ {
+ return 0;
+ }
+
+ int celtDecoderSize = GetCeltDecoderSize(channelsCount);
+ int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x4c;
+
+ return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
+ }
+
+ private static int GetOpusDecoderAllocSize(int channelsCount)
+ {
+ return (channelsCount * 0x800 + 0x4803) & -0x800;
+ }
+
+ private static int GetCeltDecoderSize(int channelsCount)
+ {
+ const int DecodeBufferSize = 0x2030;
+ const int Overlap = 120;
+ const int EBandsCount = 21;
+
+ return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x50;
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs
new file mode 100644
index 00000000..fd2091c2
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs
@@ -0,0 +1,21 @@
+namespace Ryujinx.HLE.HOS.Services.Audio
+{
+ enum ResultCode
+ {
+ ModuleId = 153,
+ ErrorCodeShift = 9,
+
+ Success = 0,
+
+ DeviceNotFound = (1 << ErrorCodeShift) | ModuleId,
+ UnsupportedRevision = (2 << ErrorCodeShift) | ModuleId,
+ UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId,
+ BufferSizeTooSmall = (4 << ErrorCodeShift) | ModuleId,
+ OpusInvalidInput = (6 << ErrorCodeShift) | ModuleId,
+ TooManyBuffersInUse = (8 << ErrorCodeShift) | ModuleId,
+ InvalidChannelCount = (10 << ErrorCodeShift) | ModuleId,
+ InvalidOperation = (513 << ErrorCodeShift) | ModuleId,
+ InvalidHandle = (1536 << ErrorCodeShift) | ModuleId,
+ OutputAlreadyStarted = (1540 << ErrorCodeShift) | ModuleId
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs
new file mode 100644
index 00000000..e49c294c
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.Types
+{
+ [Flags]
+ enum OpusDecoderFlags : uint
+ {
+ None,
+ LargeFrameSize = 1 << 0,
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs
new file mode 100644
index 00000000..fd63a4f7
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x110)]
+ struct OpusMultiStreamParameters
+ {
+ public int SampleRate;
+ public int ChannelsCount;
+ public int NumberOfStreams;
+ public int NumberOfStereoStreams;
+ public Array64<uint> ChannelMappings;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs
new file mode 100644
index 00000000..1315c734
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs
@@ -0,0 +1,19 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x118)]
+ struct OpusMultiStreamParametersEx
+ {
+ public int SampleRate;
+ public int ChannelsCount;
+ public int NumberOfStreams;
+ public int NumberOfStereoStreams;
+ public OpusDecoderFlags Flags;
+
+ Array4<byte> Padding1;
+
+ public Array64<uint> ChannelMappings;
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs
new file mode 100644
index 00000000..5ae0eb1e
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs
@@ -0,0 +1,23 @@
+using System;
+using System.Buffers.Binary;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.Types
+{
+ [StructLayout(LayoutKind.Sequential)]
+ struct OpusPacketHeader
+ {
+ public uint length;
+ public uint finalRange;
+
+ public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
+ {
+ OpusPacketHeader header = MemoryMarshal.Cast<byte, OpusPacketHeader>(data)[0];
+
+ header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length;
+ header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange;
+
+ return header;
+ }
+ }
+}
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs
new file mode 100644
index 00000000..f088ed01
--- /dev/null
+++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs
@@ -0,0 +1,15 @@
+using Ryujinx.Common.Memory;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.HLE.HOS.Services.Audio.Types
+{
+ [StructLayout(LayoutKind.Sequential, Size = 0x10)]
+ struct OpusParametersEx
+ {
+ public int SampleRate;
+ public int ChannelsCount;
+ public OpusDecoderFlags Flags;
+
+ Array4<byte> Padding1;
+ }
+}