diff options
Diffstat (limited to 'src/Ryujinx.Horizon/Sdk')
41 files changed, 2678 insertions, 1 deletions
diff --git a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs index ada2c02b..d612f479 100644 --- a/src/Ryujinx.Horizon/Sdk/Account/Uid.cs +++ b/src/Ryujinx.Horizon/Sdk/Account/Uid.cs @@ -5,7 +5,7 @@ using System.Runtime.InteropServices; namespace Ryujinx.Horizon.Sdk.Account { - [StructLayout(LayoutKind.Sequential)] + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)] public readonly record struct Uid { public readonly ulong High; diff --git a/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs b/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs new file mode 100644 index 00000000..2b81fbf6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs @@ -0,0 +1,71 @@ +namespace Ryujinx.Horizon.Sdk.Applet +{ + enum AppletId : uint + { + None = 0x00, + Application = 0x01, + OverlayApplet = 0x02, + SystemAppletMenu = 0x03, + SystemApplication = 0x04, + LibraryAppletAuth = 0x0A, + LibraryAppletCabinet = 0x0B, + LibraryAppletController = 0x0C, + LibraryAppletDataErase = 0x0D, + LibraryAppletError = 0x0E, + LibraryAppletNetConnect = 0x0F, + LibraryAppletPlayerSelect = 0x10, + LibraryAppletSwkbd = 0x11, + LibraryAppletMiiEdit = 0x12, + LibraryAppletWeb = 0x13, + LibraryAppletShop = 0x14, + LibraryAppletPhotoViewer = 0x15, + LibraryAppletSet = 0x16, + LibraryAppletOfflineWeb = 0x17, + LibraryAppletLoginShare = 0x18, + LibraryAppletWifiWebAuth = 0x19, + LibraryAppletMyPage = 0x1A, + LibraryAppletGift = 0x1B, + LibraryAppletUserMigration = 0x1C, + LibraryAppletPreomiaSys = 0x1D, + LibraryAppletStory = 0x1E, + LibraryAppletPreomiaUsr = 0x1F, + LibraryAppletPreomiaUsrDummy = 0x20, + LibraryAppletSample = 0x21, + LibraryAppletPromoteQualification = 0x22, + LibraryAppletOfflineWebFw17 = 0x32, + LibraryAppletOfflineWeb2Fw17 = 0x33, + LibraryAppletLoginShareFw17 = 0x35, + LibraryAppletLoginShare2Fw17 = 0x36, + LibraryAppletLoginShare3Fw17 = 0x37, + Unknown38 = 0x38, + DevlopmentTool = 0x3E8, + CombinationLA = 0x3F1, + AeSystemApplet = 0x3F2, + AeOverlayApplet = 0x3F3, + AeStarter = 0x3F4, + AeLibraryAppletAlone = 0x3F5, + AeLibraryApplet1 = 0x3F6, + AeLibraryApplet2 = 0x3F7, + AeLibraryApplet3 = 0x3F8, + AeLibraryApplet4 = 0x3F9, + AppletISA = 0x3FA, + AppletIOA = 0x3FB, + AppletISTA = 0x3FC, + AppletILA1 = 0x3FD, + AppletILA2 = 0x3FE, + CombinationLAFw17 = 0x700000DC, + AeSystemAppletFw17 = 0x700000E6, + AeOverlayAppletFw17 = 0x700000E7, + AeStarterFw17 = 0x700000E8, + AeLibraryAppletAloneFw17 = 0x700000E9, + AeLibraryApplet1Fw17 = 0x700000EA, + AeLibraryApplet2Fw17 = 0x700000EB, + AeLibraryApplet3Fw17 = 0x700000EC, + AeLibraryApplet4Fw17 = 0x700000ED, + AppletISAFw17 = 0x700000F0, + AppletIOAFw17 = 0x700000F1, + AppletISTAFw17 = 0x700000F2, + AppletILA1Fw17 = 0x700000F3, + AppletILA2Fw17 = 0x700000F4, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs b/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs new file mode 100644 index 00000000..00e2ad36 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs @@ -0,0 +1,15 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Applet +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)] + readonly struct AppletResourceUserId + { + public readonly ulong Id; + + public AppletResourceUserId(ulong id) + { + Id = id; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs new file mode 100644 index 00000000..efa8d5bc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs @@ -0,0 +1,50 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio +{ + class AudioEvent : IWritableEvent, IDisposable + { + private SystemEventType _systemEvent; + private readonly IExternalEvent _externalEvent; + + public AudioEvent() + { + Os.CreateSystemEvent(out _systemEvent, EventClearMode.ManualClear, interProcess: true); + + // We need to do this because the event will be signalled from a different thread. + _externalEvent = HorizonStatic.Syscall.GetExternalEvent(Os.GetWritableHandleOfSystemEvent(ref _systemEvent)); + } + + public void Signal() + { + _externalEvent.Signal(); + } + + public void Clear() + { + _externalEvent.Clear(); + } + + public int GetReadableHandle() + { + return Os.GetReadableHandleOfSystemEvent(ref _systemEvent); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _systemEvent); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs new file mode 100644 index 00000000..c18bfee9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Audio +{ + static class AudioResult + { + private const int ModuleId = 153; + + public static Result DeviceNotFound => new(ModuleId, 1); + public static Result UnsupportedRevision => new(ModuleId, 2); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs new file mode 100644 index 00000000..f67ea729 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs @@ -0,0 +1,252 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioDevice : IAudioDevice, IDisposable + { + private readonly VirtualDeviceSessionRegistry _registry; + private readonly VirtualDeviceSession[] _sessions; + private readonly bool _isUsbDeviceSupported; + + private SystemEventType _audioEvent; + private SystemEventType _audioInputEvent; + private SystemEventType _audioOutputEvent; + + public AudioDevice(VirtualDeviceSessionRegistry registry, AppletResourceUserId appletResourceId, uint revision) + { + _registry = registry; + + BehaviourContext behaviourContext = new(); + behaviourContext.SetUserRevision((int)revision); + + _isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported(); + _sessions = registry.GetSessionByAppletResourceId(appletResourceId.Id); + + Os.CreateSystemEvent(out _audioEvent, EventClearMode.AutoClear, interProcess: true); + Os.CreateSystemEvent(out _audioInputEvent, EventClearMode.AutoClear, interProcess: true); + Os.CreateSystemEvent(out _audioOutputEvent, EventClearMode.AutoClear, interProcess: true); + } + + 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; + } + + [CmifCommand(0)] + public Result ListAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names, out int nameCount) + { + int count = 0; + + foreach (VirtualDeviceSession session in _sessions) + { + if (!_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + continue; + } + + if (count >= names.Length) + { + break; + } + + names[count] = new DeviceName(session.Device.Name); + + count++; + } + + nameCount = count; + + return Result.Success; + } + + [CmifCommand(1)] + public Result SetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, float volume) + { + if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString(), ignoreRevLimitation: true)) + { + if (!_isUsbDeviceSupported && result.Device.IsUsbDevice()) + { + result = _sessions[0]; + } + + result.Volume = volume; + } + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, out float volume) + { + if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString())) + { + volume = result.Volume; + } + else + { + volume = 0f; + } + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetActiveAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> name) + { + VirtualDevice device = _registry.ActiveDevice; + + if (!_isUsbDeviceSupported && device.IsUsbDevice()) + { + device = _registry.DefaultDevice; + } + + if (name.Length > 0) + { + name[0] = new DeviceName(device.Name); + } + + return Result.Success; + } + + [CmifCommand(4)] + public Result QueryAudioDeviceSystemEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioEvent); + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetActiveChannelCount(out int channelCount) + { + VirtualDevice device = _registry.ActiveDevice; + + if (!_isUsbDeviceSupported && device.IsUsbDevice()) + { + device = _registry.DefaultDevice; + } + + channelCount = (int)device.ChannelCount; + + return Result.Success; + } + + [CmifCommand(6)] // 3.0.0+ + public Result ListAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names, out int nameCount) + { + return ListAudioDeviceName(names, out nameCount); + } + + [CmifCommand(7)] // 3.0.0+ + public Result SetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, float volume) + { + return SetAudioDeviceOutputVolume(name, volume); + } + + [CmifCommand(8)] // 3.0.0+ + public Result GetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, out float volume) + { + return GetAudioDeviceOutputVolume(name, out volume); + } + + [CmifCommand(10)] // 3.0.0+ + public Result GetActiveAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> name) + { + return GetActiveAudioDeviceName(name); + } + + [CmifCommand(11)] // 3.0.0+ + public Result QueryAudioDeviceInputEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioInputEvent); + + return Result.Success; + } + + [CmifCommand(12)] // 3.0.0+ + public Result QueryAudioDeviceOutputEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioOutputEvent); + + return Result.Success; + } + + [CmifCommand(13)] // 13.0.0+ + public Result GetActiveAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> name) + { + if (name.Length > 0) + { + name[0] = new DeviceName(_registry.ActiveDevice.GetOutputDeviceName()); + } + + return Result.Success; + } + + [CmifCommand(14)] // 13.0.0+ + public Result ListAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names, out int nameCount) + { + int count = 0; + + foreach (VirtualDeviceSession session in _sessions) + { + if (!_isUsbDeviceSupported && session.Device.IsUsbDevice()) + { + continue; + } + + if (count >= names.Length) + { + break; + } + + names[count] = new DeviceName(session.Device.GetOutputDeviceName()); + + count++; + } + + nameCount = count; + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _audioEvent); + Os.DestroySystemEvent(ref _audioInputEvent); + Os.DestroySystemEvent(ref _audioOutputEvent); + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs new file mode 100644 index 00000000..464ede58 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs @@ -0,0 +1,171 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Input; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioIn : IAudioIn, IDisposable + { + private readonly AudioInputSystem _impl; + private int _processHandle; + + public AudioIn(AudioInputSystem impl, int processHandle) + { + _impl = impl; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetAudioInState(out AudioDeviceState state) + { + state = _impl.GetState(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + return new Result((int)_impl.Start()); + } + + [CmifCommand(2)] + public Result Stop() + { + return new Result((int)_impl.Stop()); + } + + [CmifCommand(3)] + public Result AppendAudioInBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer)); + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = 0; + + if (_impl.RegisterBufferEvent() is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedAudioInBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ulong> bufferTags) + { + return new Result((int)_impl.GetReleasedBuffers(bufferTags, out count)); + } + + [CmifCommand(6)] + public Result ContainsAudioInBuffer(out bool contains, ulong bufferTag) + { + contains = _impl.ContainsBuffer(bufferTag); + + return Result.Success; + } + + [CmifCommand(7)] // 3.0.0+ + public Result AppendUacInBuffer( + ulong bufferTag, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer, + [CopyHandle] int eventHandle) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendUacBuffer(bufferTag, ref userBuffer, (uint)eventHandle)); + } + + [CmifCommand(8)] // 3.0.0+ + public Result AppendAudioInBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer) + { + return AppendAudioInBuffer(bufferTag, buffer); + } + + [CmifCommand(9)] // 3.0.0+ + public Result GetReleasedAudioInBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<ulong> bufferTags) + { + return GetReleasedAudioInBuffers(out count, bufferTags); + } + + [CmifCommand(10)] // 3.0.0+ + public Result AppendUacInBufferAuto( + ulong bufferTag, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer, + [CopyHandle] int eventHandle) + { + return AppendUacInBuffer(bufferTag, buffer, eventHandle); + } + + [CmifCommand(11)] // 4.0.0+ + public Result GetAudioInBufferCount(out uint bufferCount) + { + bufferCount = _impl.GetBufferCount(); + + return Result.Success; + } + + [CmifCommand(12)] // 4.0.0+ + public Result SetDeviceGain(float gain) + { + _impl.SetVolume(gain); + + return Result.Success; + } + + [CmifCommand(13)] // 4.0.0+ + public Result GetDeviceGain(out float gain) + { + gain = _impl.GetVolume(); + + return Result.Success; + } + + [CmifCommand(14)] // 6.0.0+ + public Result FlushAudioInBuffers(out bool pending) + { + pending = _impl.FlushBuffers(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs new file mode 100644 index 00000000..d5d04720 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs @@ -0,0 +1,130 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Input; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioInManager : IAudioInManager + { + private readonly AudioInputManager _impl; + + public AudioInManager(AudioInputManager impl) + { + _impl = impl; + } + + [CmifCommand(0)] + public Result ListAudioIns(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names) + { + string[] deviceNames = _impl.ListAudioIns(filtered: false); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result OpenAudioIn( + out AudioOutputConfiguration outputConfiguration, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + ResultCode rc = _impl.OpenAudioIn( + out string outputDeviceName, + out outputConfiguration, + out AudioInputSystem inSystem, + clientMemoryManager, + name.Length > 0 ? name[0].ToString() : string.Empty, + SampleFormat.PcmInt16, + ref parameter); + + if (rc == ResultCode.Success && outName.Length > 0) + { + outName[0] = new DeviceName(outputDeviceName); + } + + audioIn = new AudioIn(inSystem, processHandle); + + return new Result((int)rc); + } + + [CmifCommand(2)] // 3.0.0+ + public Result ListAudioInsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names) + { + return ListAudioIns(out count, names); + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioInAuto( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, + [ClientProcessId] ulong pid) + { + return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid); + } + + [CmifCommand(4)] // 3.0.0+ + public Result ListAudioInsAutoFiltered(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names) + { + string[] deviceNames = _impl.ListAudioIns(filtered: true); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(5)] // 5.0.0+ + public Result OpenAudioInProtocolSpecified( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName, + AudioInProtocol protocol, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, + [ClientProcessId] ulong pid) + { + // NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices). + + return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs new file mode 100644 index 00000000..48785f1c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs @@ -0,0 +1,23 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)] + struct AudioInProtocol + { + public AudioInProtocolName Name; + public Array7<byte> Padding; + + public AudioInProtocol(AudioInProtocolName name) + { + Name = name; + Padding = new(); + } + + public override readonly string ToString() + { + return Name.ToString(); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs new file mode 100644 index 00000000..68d283cc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocolName.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + enum AudioInProtocolName : byte + { + DeviceIn = 0, + UacIn = 1, + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs new file mode 100644 index 00000000..7607e264 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs @@ -0,0 +1,154 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Output; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioOut : IAudioOut, IDisposable + { + private readonly AudioOutputSystem _impl; + private int _processHandle; + + public AudioOut(AudioOutputSystem impl, int processHandle) + { + _impl = impl; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetAudioOutState(out AudioDeviceState state) + { + state = _impl.GetState(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + return new Result((int)_impl.Start()); + } + + [CmifCommand(2)] + public Result Stop() + { + return new Result((int)_impl.Stop()); + } + + [CmifCommand(3)] + public Result AppendAudioOutBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer) + { + AudioUserBuffer userBuffer = default; + + if (buffer.Length > 0) + { + userBuffer = buffer[0]; + } + + return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer)); + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = 0; + + if (_impl.RegisterBufferEvent() is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedAudioOutBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ulong> bufferTags) + { + return new Result((int)_impl.GetReleasedBuffer(bufferTags, out count)); + } + + [CmifCommand(6)] + public Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag) + { + contains = _impl.ContainsBuffer(bufferTag); + + return Result.Success; + } + + [CmifCommand(7)] // 3.0.0+ + public Result AppendAudioOutBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer) + { + return AppendAudioOutBuffer(bufferTag, buffer); + } + + [CmifCommand(8)] // 3.0.0+ + public Result GetReleasedAudioOutBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<ulong> bufferTags) + { + return GetReleasedAudioOutBuffers(out count, bufferTags); + } + + [CmifCommand(9)] // 4.0.0+ + public Result GetAudioOutBufferCount(out uint bufferCount) + { + bufferCount = _impl.GetBufferCount(); + + return Result.Success; + } + + [CmifCommand(10)] // 4.0.0+ + public Result GetAudioOutPlayedSampleCount(out ulong sampleCount) + { + sampleCount = _impl.GetPlayedSampleCount(); + + return Result.Success; + } + + [CmifCommand(11)] // 4.0.0+ + public Result FlushAudioOutBuffers(out bool pending) + { + pending = _impl.FlushBuffers(); + + return Result.Success; + } + + [CmifCommand(12)] // 6.0.0+ + public Result SetAudioOutVolume(float volume) + { + _impl.SetVolume(volume); + + return Result.Success; + } + + [CmifCommand(13)] // 6.0.0+ + public Result GetAudioOutVolume(out float volume) + { + volume = _impl.GetVolume(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs new file mode 100644 index 00000000..3d129470 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs @@ -0,0 +1,93 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Common; +using Ryujinx.Audio.Output; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioOutManager : IAudioOutManager + { + private readonly AudioOutputManager _impl; + + public AudioOutManager(AudioOutputManager impl) + { + _impl = impl; + } + + [CmifCommand(0)] + public Result ListAudioOuts(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names) + { + string[] deviceNames = _impl.ListAudioOuts(); + + count = 0; + + foreach (string deviceName in deviceNames) + { + if (count >= names.Length) + { + break; + } + + names[count++] = new DeviceName(deviceName); + } + + return Result.Success; + } + + [CmifCommand(1)] + public Result OpenAudioOut( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + ResultCode rc = _impl.OpenAudioOut( + out string outputDeviceName, + out outputConfig, + out AudioOutputSystem outSystem, + clientMemoryManager, + name.Length > 0 ? name[0].ToString() : string.Empty, + SampleFormat.PcmInt16, + ref parameter); + + if (rc == ResultCode.Success && outName.Length > 0) + { + outName[0] = new DeviceName(outputDeviceName); + } + + audioOut = new AudioOut(outSystem, processHandle); + + return new Result((int)rc); + } + + [CmifCommand(2)] // 3.0.0+ + public Result ListAudioOutsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names) + { + return ListAudioOuts(out count, names); + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioOutAuto( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + [CopyHandle] int processHandle, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, + [ClientProcessId] ulong pid) + { + return OpenAudioOut(out outputConfig, out audioOut, outName, parameter, appletResourceId, processHandle, name, pid); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs new file mode 100644 index 00000000..776df641 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs @@ -0,0 +1,187 @@ +using Ryujinx.Audio; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common.Memory; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Buffers; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioRenderer : IAudioRenderer, IDisposable + { + private readonly AudioRenderSystem _renderSystem; + private int _workBufferHandle; + private int _processHandle; + + public AudioRenderer(AudioRenderSystem renderSystem, int workBufferHandle, int processHandle) + { + _renderSystem = renderSystem; + _workBufferHandle = workBufferHandle; + _processHandle = processHandle; + } + + [CmifCommand(0)] + public Result GetSampleRate(out int sampleRate) + { + sampleRate = (int)_renderSystem.GetSampleRate(); + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetSampleCount(out int sampleCount) + { + sampleCount = (int)_renderSystem.GetSampleCount(); + + return Result.Success; + } + + [CmifCommand(2)] + public Result GetMixBufferCount(out int mixBufferCount) + { + mixBufferCount = (int)_renderSystem.GetMixBufferCount(); + + return Result.Success; + } + + [CmifCommand(3)] + public Result GetState(out int state) + { + state = _renderSystem.IsActive() ? 0 : 1; + + return Result.Success; + } + + [CmifCommand(4)] + public Result RequestUpdate( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input) + { + using IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(output.Length); + using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length); + + Memory<byte> outputMemory = outputOwner.Memory; + Memory<byte> performanceOutputMemory = performanceOutputOwner.Memory; + + using MemoryHandle outputHandle = outputMemory.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin(); + + Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray())); + + outputMemory.Span.CopyTo(output); + performanceOutputMemory.Span.CopyTo(performanceOutput); + + return result; + } + + [CmifCommand(5)] + public Result Start() + { + _renderSystem.Start(); + + return Result.Success; + } + + [CmifCommand(6)] + public Result Stop() + { + _renderSystem.Stop(); + + return Result.Success; + } + + [CmifCommand(7)] + public Result QuerySystemEvent([CopyHandle] out int eventHandle) + { + ResultCode rc = _renderSystem.QuerySystemEvent(out IWritableEvent systemEvent); + + eventHandle = 0; + + if (rc == ResultCode.Success && systemEvent is AudioEvent audioEvent) + { + eventHandle = audioEvent.GetReadableHandle(); + } + + return new Result((int)rc); + } + + [CmifCommand(8)] + public Result SetRenderingTimeLimit(int percent) + { + _renderSystem.SetRenderingTimeLimitPercent((uint)percent); + + return Result.Success; + } + + [CmifCommand(9)] + public Result GetRenderingTimeLimit(out int percent) + { + percent = (int)_renderSystem.GetRenderingTimeLimit(); + + return Result.Success; + } + + [CmifCommand(10)] // 3.0.0+ + public Result RequestUpdateAuto( + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> input) + { + return RequestUpdate(output, performanceOutput, input); + } + + [CmifCommand(11)] // 3.0.0+ + public Result ExecuteAudioRendererRendering() + { + return new Result((int)_renderSystem.ExecuteAudioRendererRendering()); + } + + [CmifCommand(12)] // 15.0.0+ + public Result SetVoiceDropParameter(float voiceDropParameter) + { + _renderSystem.SetVoiceDropParameter(voiceDropParameter); + + return Result.Success; + } + + [CmifCommand(13)] // 15.0.0+ + public Result GetVoiceDropParameter(out float voiceDropParameter) + { + voiceDropParameter = _renderSystem.GetVoiceDropParameter(); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _renderSystem.Dispose(); + + if (_workBufferHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_workBufferHandle); + + _workBufferHandle = 0; + } + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs new file mode 100644 index 00000000..7138d27c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs @@ -0,0 +1,132 @@ +using Ryujinx.Audio.Renderer.Device; +using Ryujinx.Audio.Renderer.Server; +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioRendererManager : IAudioRendererManager + { + private const uint InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24); + + private readonly Ryujinx.Audio.Renderer.Server.AudioRendererManager _impl; + private readonly VirtualDeviceSessionRegistry _registry; + + public AudioRendererManager(Ryujinx.Audio.Renderer.Server.AudioRendererManager impl, VirtualDeviceSessionRegistry registry) + { + _impl = impl; + _registry = registry; + } + + [CmifCommand(0)] + public Result OpenAudioRenderer( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + [CopyHandle] int workBufferHandle, + [CopyHandle] int processHandle, + ulong workBufferSize, + AppletResourceUserId appletResourceId, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + ulong workBufferAddress = HorizonStatic.Syscall.GetTransferMemoryAddress(workBufferHandle); + + Result result = new Result((int)_impl.OpenAudioRenderer( + out var renderSystem, + clientMemoryManager, + ref parameter.Configuration, + appletResourceId.Id, + workBufferAddress, + workBufferSize, + (uint)processHandle)); + + if (result.IsSuccess) + { + renderer = new AudioRenderer(renderSystem, workBufferHandle, processHandle); + } + else + { + renderer = null; + + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + HorizonStatic.Syscall.CloseHandle(processHandle); + } + + return result; + } + + [CmifCommand(1)] + public Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter) + { + if (BehaviourContext.CheckValidRevision(parameter.Configuration.Revision)) + { + workBufferSize = (long)Ryujinx.Audio.Renderer.Server.AudioRendererManager.GetWorkBufferSize(ref parameter.Configuration); + + Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{workBufferSize:x16}."); + + return Result.Success; + } + else + { + workBufferSize = 0; + + Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Configuration.Revision)} is not supported!"); + + return AudioResult.UnsupportedRevision; + } + } + + [CmifCommand(2)] + public Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId) + { + audioDevice = new AudioDevice(_registry, appletResourceId, InitialRevision); + + return Result.Success; + } + + [CmifCommand(3)] // 3.0.0+ + public Result OpenAudioRendererForManualExecution( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + ulong workBufferAddress, + [CopyHandle] int processHandle, + ulong workBufferSize, + AppletResourceUserId appletResourceId, + [ClientProcessId] ulong pid) + { + var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle); + + Result result = new Result((int)_impl.OpenAudioRenderer( + out var renderSystem, + clientMemoryManager, + ref parameter.Configuration, + appletResourceId.Id, + workBufferAddress, + workBufferSize, + (uint)processHandle)); + + if (result.IsSuccess) + { + renderer = new AudioRenderer(renderSystem, 0, processHandle); + } + else + { + renderer = null; + + HorizonStatic.Syscall.CloseHandle(processHandle); + } + + return result; + } + + [CmifCommand(4)] // 4.0.0+ + public Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId, uint revision) + { + audioDevice = new AudioDevice(_registry, appletResourceId, revision); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs new file mode 100644 index 00000000..e5fcf7b3 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererParameterInternal.cs @@ -0,0 +1,14 @@ +using Ryujinx.Audio.Renderer.Parameter; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + struct AudioRendererParameterInternal + { + public AudioRendererConfiguration Configuration; + + public AudioRendererParameterInternal(AudioRendererConfiguration configuration) + { + Configuration = configuration; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs new file mode 100644 index 00000000..cf1fe3d1 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs @@ -0,0 +1,30 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class AudioSnoopManager : IAudioSnoopManager + { + // Note: The interface changed completely on firmware 17.0.0, this implementation is for older firmware. + + [CmifCommand(0)] + public Result EnableDspUsageMeasurement() + { + return Result.Success; + } + + [CmifCommand(1)] + public Result DisableDspUsageMeasurement() + { + return Result.Success; + } + + [CmifCommand(6)] + public Result GetDspUsage(out uint usage) + { + usage = 0; + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs new file mode 100644 index 00000000..b77e2f40 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs @@ -0,0 +1,30 @@ +using Ryujinx.Common.Memory; +using System; +using System.Runtime.InteropServices; +using System.Text; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x100, Pack = 1)] + struct DeviceName + { + public Array256<byte> Name; + + public DeviceName(string name) + { + Name = new(); + Encoding.ASCII.GetBytes(name, Name.AsSpan()); + } + + public override string ToString() + { + int length = Name.AsSpan().IndexOf((byte)0); + if (length < 0) + { + length = 0x100; + } + + return Encoding.ASCII.GetString(Name.AsSpan()[..length]); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs new file mode 100644 index 00000000..39391437 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs @@ -0,0 +1,147 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.OsTypes; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class FinalOutputRecorder : IFinalOutputRecorder, IDisposable + { + private int _processHandle; + private SystemEventType _event; + + public FinalOutputRecorder(int processHandle) + { + _processHandle = processHandle; + Os.CreateSystemEvent(out _event, EventClearMode.ManualClear, interProcess: true); + } + + [CmifCommand(0)] + public Result GetFinalOutputRecorderState(out uint state) + { + state = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(1)] + public Result Start() + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(2)] + public Result Stop() + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(3)] + public Result AppendFinalOutputRecorderBuffer([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> buffer, ulong bufferClientPtr) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferClientPtr }); + + return Result.Success; + } + + [CmifCommand(4)] + public Result RegisterBufferEvent([CopyHandle] out int eventHandle) + { + eventHandle = Os.GetReadableHandleOfSystemEvent(ref _event); + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(5)] + public Result GetReleasedFinalOutputRecorderBuffers([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> buffer, out uint count, out ulong released) + { + count = 0; + released = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(6)] + public Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains) + { + contains = false; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer }); + + return Result.Success; + } + + [CmifCommand(7)] + public Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released) + { + released = 0; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer }); + + return Result.Success; + } + + [CmifCommand(8)] // 3.0.0+ + public Result AppendFinalOutputRecorderBufferAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> buffer, ulong bufferClientPtr) + { + return AppendFinalOutputRecorderBuffer(buffer, bufferClientPtr); + } + + [CmifCommand(9)] // 3.0.0+ + public Result GetReleasedFinalOutputRecorderBuffersAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> buffer, out uint count, out ulong released) + { + return GetReleasedFinalOutputRecorderBuffers(buffer, out count, out released); + } + + [CmifCommand(10)] // 6.0.0+ + public Result FlushFinalOutputRecorderBuffers(out bool pending) + { + pending = false; + + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(11)] // 9.0.0+ + public Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { parameter }); + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + Os.DestroySystemEvent(ref _event); + + if (_processHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_processHandle); + + _processHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs new file mode 100644 index 00000000..76491bb7 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderManager.cs @@ -0,0 +1,23 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + partial class FinalOutputRecorderManager : IFinalOutputRecorderManager + { + [CmifCommand(0)] + public Result OpenFinalOutputRecorder( + out IFinalOutputRecorder recorder, + FinalOutputRecorderParameter parameter, + [CopyHandle] int processHandle, + out FinalOutputRecorderParameterInternal outParameter, + AppletResourceUserId appletResourceId) + { + recorder = new FinalOutputRecorder(processHandle); + outParameter = new(parameter.SampleRate, 2, 0); + + return Result.Success; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs new file mode 100644 index 00000000..afa060fc --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameter.cs @@ -0,0 +1,17 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + readonly struct FinalOutputRecorderParameter + { + public readonly uint SampleRate; + public readonly uint Padding; + + public FinalOutputRecorderParameter(uint sampleRate) + { + SampleRate = sampleRate; + Padding = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs new file mode 100644 index 00000000..e88398eb --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorderParameterInternal.cs @@ -0,0 +1,21 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)] + readonly struct FinalOutputRecorderParameterInternal + { + public readonly uint SampleRate; + public readonly uint ChannelCount; + public readonly uint UseLargeFrameSize; + public readonly uint Padding; + + public FinalOutputRecorderParameterInternal(uint sampleRate, uint channelCount, uint useLargeFrameSize) + { + SampleRate = sampleRate; + ChannelCount = channelCount; + UseLargeFrameSize = useLargeFrameSize; + Padding = 0; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs new file mode 100644 index 00000000..3df1fe22 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs @@ -0,0 +1,24 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioDevice : IServiceObject + { + Result ListAudioDeviceName(Span<DeviceName> names, out int nameCount); + Result SetAudioDeviceOutputVolume(ReadOnlySpan<DeviceName> name, float volume); + Result GetAudioDeviceOutputVolume(ReadOnlySpan<DeviceName> name, out float volume); + Result GetActiveAudioDeviceName(Span<DeviceName> name); + Result QueryAudioDeviceSystemEvent(out int eventHandle); + Result GetActiveChannelCount(out int channelCount); + Result ListAudioDeviceNameAuto(Span<DeviceName> names, out int nameCount); + Result SetAudioDeviceOutputVolumeAuto(ReadOnlySpan<DeviceName> name, float volume); + Result GetAudioDeviceOutputVolumeAuto(ReadOnlySpan<DeviceName> name, out float volume); + Result GetActiveAudioDeviceNameAuto(Span<DeviceName> name); + Result QueryAudioDeviceInputEvent(out int eventHandle); + Result QueryAudioDeviceOutputEvent(out int eventHandle); + Result GetActiveAudioOutputDeviceName(Span<DeviceName> name); + Result ListAudioOutputDeviceName(Span<DeviceName> names, out int nameCount); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs new file mode 100644 index 00000000..bdc3bcf6 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs @@ -0,0 +1,26 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioIn : IServiceObject + { + Result GetAudioInState(out AudioDeviceState state); + Result Start(); + Result Stop(); + Result AppendAudioInBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedAudioInBuffers(out uint count, Span<ulong> bufferTags); + Result ContainsAudioInBuffer(out bool contains, ulong bufferTag); + Result AppendUacInBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer, int eventHandle); + Result AppendAudioInBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer); + Result GetReleasedAudioInBuffersAuto(out uint count, Span<ulong> bufferTags); + Result AppendUacInBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer, int eventHandle); + Result GetAudioInBufferCount(out uint bufferCount); + Result SetDeviceGain(float gain); + Result GetDeviceGain(out float gain); + Result FlushAudioInBuffers(out bool pending); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs new file mode 100644 index 00000000..e7f32fbd --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs @@ -0,0 +1,43 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioInManager : IServiceObject + { + Result ListAudioIns(out int count, Span<DeviceName> names); + Result OpenAudioIn( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span<DeviceName> outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan<DeviceName> name, + ulong pid); + Result ListAudioInsAuto(out int count, Span<DeviceName> names); + Result OpenAudioInAuto( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span<DeviceName> outName, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan<DeviceName> name, + ulong pid); + Result ListAudioInsAutoFiltered(out int count, Span<DeviceName> names); + Result OpenAudioInProtocolSpecified( + out AudioOutputConfiguration outputConfig, + out IAudioIn audioIn, + Span<DeviceName> outName, + AudioInProtocol protocol, + AudioInputConfiguration parameter, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan<DeviceName> name, + ulong pid); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs new file mode 100644 index 00000000..1b200926 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs @@ -0,0 +1,25 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioOut : IServiceObject + { + Result GetAudioOutState(out AudioDeviceState state); + Result Start(); + Result Stop(); + Result AppendAudioOutBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedAudioOutBuffers(out uint count, Span<ulong> bufferTags); + Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag); + Result AppendAudioOutBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer); + Result GetReleasedAudioOutBuffersAuto(out uint count, Span<ulong> bufferTags); + Result GetAudioOutBufferCount(out uint bufferCount); + Result GetAudioOutPlayedSampleCount(out ulong sampleCount); + Result FlushAudioOutBuffers(out bool pending); + Result SetAudioOutVolume(float volume); + Result GetAudioOutVolume(out float volume); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs new file mode 100644 index 00000000..40d62836 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs @@ -0,0 +1,32 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioOutManager : IServiceObject + { + Result ListAudioOuts(out int count, Span<DeviceName> names); + Result OpenAudioOut( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + Span<DeviceName> outName, + AudioInputConfiguration inputConfig, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan<DeviceName> name, + ulong pid); + Result ListAudioOutsAuto(out int count, Span<DeviceName> names); + Result OpenAudioOutAuto( + out AudioOutputConfiguration outputConfig, + out IAudioOut audioOut, + Span<DeviceName> outName, + AudioInputConfiguration inputConfig, + AppletResourceUserId appletResourceId, + int processHandle, + ReadOnlySpan<DeviceName> name, + ulong pid); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs new file mode 100644 index 00000000..e4ca2e8e --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs @@ -0,0 +1,24 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioRenderer : IServiceObject + { + Result GetSampleRate(out int sampleRate); + Result GetSampleCount(out int sampleCount); + Result GetMixBufferCount(out int mixBufferCount); + Result GetState(out int state); + Result RequestUpdate(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input); + Result Start(); + Result Stop(); + Result QuerySystemEvent(out int eventHandle); + Result SetRenderingTimeLimit(int percent); + Result GetRenderingTimeLimit(out int percent); + Result RequestUpdateAuto(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input); + Result ExecuteAudioRendererRendering(); + Result SetVoiceDropParameter(float voiceDropParameter); + Result GetVoiceDropParameter(out float voiceDropParameter); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs new file mode 100644 index 00000000..fe95a208 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRendererManager.cs @@ -0,0 +1,29 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioRendererManager : IServiceObject + { + Result OpenAudioRenderer( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + int processHandle, + int workBufferHandle, + ulong workBufferSize, + AppletResourceUserId appletUserId, + ulong pid); + Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter); + Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletUserId); + Result OpenAudioRendererForManualExecution( + out IAudioRenderer renderer, + AudioRendererParameterInternal parameter, + ulong workBufferAddress, + int processHandle, + ulong workBufferSize, + AppletResourceUserId appletUserId, + ulong pid); + Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletUserId, uint revision); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs new file mode 100644 index 00000000..72853886 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs @@ -0,0 +1,12 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IAudioSnoopManager : IServiceObject + { + Result EnableDspUsageMeasurement(); + Result DisableDspUsageMeasurement(); + Result GetDspUsage(out uint usage); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs new file mode 100644 index 00000000..be21c38b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs @@ -0,0 +1,22 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IFinalOutputRecorder : IServiceObject + { + Result GetFinalOutputRecorderState(out uint state); + Result Start(); + Result Stop(); + Result AppendFinalOutputRecorderBuffer(ReadOnlySpan<byte> buffer, ulong bufferClientPtr); + Result RegisterBufferEvent(out int eventHandle); + Result GetReleasedFinalOutputRecorderBuffers(Span<byte> buffer, out uint count, out ulong released); + Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains); + Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released); + Result AppendFinalOutputRecorderBufferAuto(ReadOnlySpan<byte> buffer, ulong bufferClientPtr); + Result GetReleasedFinalOutputRecorderBuffersAuto(Span<byte> buffer, out uint count, out ulong released); + Result FlushFinalOutputRecorderBuffers(out bool pending); + Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs new file mode 100644 index 00000000..bac41ca9 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorderManager.cs @@ -0,0 +1,16 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Applet; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Audio.Detail +{ + interface IFinalOutputRecorderManager : IServiceObject + { + Result OpenFinalOutputRecorder( + out IFinalOutputRecorder recorder, + FinalOutputRecorderParameter parameter, + int processHandle, + out FinalOutputRecorderParameterInternal outParameter, + AppletResourceUserId appletResourceId); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs new file mode 100644 index 00000000..21508b7f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs @@ -0,0 +1,16 @@ +using Ryujinx.Horizon.Common; + +namespace Ryujinx.Horizon.Sdk.Codec +{ + static class CodecResult + { + private const int ModuleId = 111; + + public static Result InvalidLength => new(ModuleId, 3); + public static Result OpusBadArg => new(ModuleId, 130); + public static Result OpusInvalidPacket => new(ModuleId, 133); + public static Result InvalidNumberOfStreams => new(ModuleId, 1000); + public static Result InvalidSampleRate => new(ModuleId, 1001); + public static Result InvalidChannelCount => new(ModuleId, 1002); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs new file mode 100644 index 00000000..5d279858 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs @@ -0,0 +1,336 @@ +using Concentus; +using Concentus.Enums; +using Concentus.Structs; +using Ryujinx.Common.Logging; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; +using System.Buffers.Binary; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + partial class HardwareOpusDecoder : IHardwareOpusDecoder, IDisposable + { + [StructLayout(LayoutKind.Sequential)] + private struct OpusPacketHeader + { + public uint Length; + public uint FinalRange; + + public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data) + { + return new() + { + Length = BinaryPrimitives.ReadUInt32BigEndian(data), + FinalRange = BinaryPrimitives.ReadUInt32BigEndian(data[sizeof(uint)..]), + }; + } + } + + private interface IDecoder + { + int SampleRate { get; } + int ChannelsCount { get; } + + int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize); + void ResetState(); + } + + private 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(); + } + } + + private 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(); + } + } + + private readonly IDecoder _decoder; + private int _workBufferHandle; + + private HardwareOpusDecoder(int workBufferHandle) + { + _workBufferHandle = workBufferHandle; + } + + public HardwareOpusDecoder(int sampleRate, int channelsCount, int workBufferHandle) : this(workBufferHandle) + { + _decoder = new Decoder(sampleRate, channelsCount); + } + + public HardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping, int workBufferHandle) : this(workBufferHandle) + { + _decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping); + } + + [CmifCommand(0)] + public Result DecodeInterleavedOld( + out int outConsumed, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false); + } + + [CmifCommand(1)] + public Result SetContext(ReadOnlySpan<byte> context) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(2)] // 3.0.0+ + public Result DecodeInterleavedForMultiStreamOld( + out int outConsumed, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out _, output, input, reset: false, withPerf: false); + } + + [CmifCommand(3)] // 3.0.0+ + public Result SetContextForMultiStream(ReadOnlySpan<byte> arg0) + { + Logger.Stub?.PrintStub(LogClass.ServiceAudio); + + return Result.Success; + } + + [CmifCommand(4)] // 4.0.0+ + public Result DecodeInterleavedWithPerfOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true); + } + + [CmifCommand(5)] // 4.0.0+ + public Result DecodeInterleavedForMultiStreamWithPerfOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset: false, withPerf: true); + } + + [CmifCommand(6)] // 6.0.0+ + public Result DecodeInterleavedWithPerfAndResetOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(7)] // 6.0.0+ + public Result DecodeInterleavedForMultiStreamWithPerfAndResetOld( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(8)] // 7.0.0+ + public Result DecodeInterleaved( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan<byte> input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + [CmifCommand(9)] // 7.0.0+ + public Result DecodeInterleavedForMultiStream( + out int outConsumed, + out long timeTaken, + out int outSamples, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] Span<byte> output, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias | HipcBufferFlags.MapTransferAllowsNonSecure)] ReadOnlySpan<byte> input, + bool reset) + { + return DecodeInterleavedInternal(out outConsumed, out outSamples, out timeTaken, output, input, reset, withPerf: true); + } + + private Result DecodeInterleavedInternal( + out int outConsumed, + out int outSamples, + out long timeTaken, + Span<byte> output, + ReadOnlySpan<byte> input, + bool reset, + bool withPerf) + { + timeTaken = 0; + + Result result = DecodeInterleaved(_decoder, reset, input, out short[] outPcmData, output.Length, out outConsumed, out outSamples); + + if (withPerf) + { + // This is the time the DSP took to process the request, TODO: fill this. + timeTaken = 0; + } + + MemoryMarshal.Cast<short, byte>(outPcmData).CopyTo(output[..(outPcmData.Length * sizeof(short))]); + + return result; + } + + private static Result GetPacketNumSamples(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 CodecResult.OpusInvalidPacket; + } + else if (result == OpusError.OPUS_BAD_ARG) + { + return CodecResult.OpusBadArg; + } + + return Result.Success; + } + + private static Result DecodeInterleaved( + IDecoder decoder, + bool reset, + ReadOnlySpan<byte> input, + out short[] outPcmData, + int outputSize, + out int outConsumed, + out int outSamples) + { + outPcmData = null; + outConsumed = 0; + outSamples = 0; + + int streamSize = input.Length; + + if (streamSize < Unsafe.SizeOf<OpusPacketHeader>()) + { + return CodecResult.InvalidLength; + } + + OpusPacketHeader header = OpusPacketHeader.FromSpan(input); + int headerSize = Unsafe.SizeOf<OpusPacketHeader>(); + uint totalSize = header.Length + (uint)headerSize; + + if (totalSize > streamSize) + { + return CodecResult.InvalidLength; + } + + byte[] opusData = input.Slice(headerSize, (int)header.Length).ToArray(); + + Result result = GetPacketNumSamples(decoder, out int numSamples, opusData); + + if (result.IsSuccess) + { + if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize) + { + return CodecResult.InvalidLength; + } + + 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 = (int)totalSize; + } + catch (OpusException) + { + // TODO: As OpusException doesn't return the exact error code, this is inaccurate in some cases... + return CodecResult.InvalidLength; + } + } + + return Result.Success; + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + if (_workBufferHandle != 0) + { + HorizonStatic.Syscall.CloseHandle(_workBufferHandle); + + _workBufferHandle = 0; + } + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs new file mode 100644 index 00000000..acec66e8 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderManager.cs @@ -0,0 +1,386 @@ +using Ryujinx.Common; +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using Ryujinx.Horizon.Sdk.Sf.Hipc; +using System; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + partial class HardwareOpusDecoderManager : IHardwareOpusDecoderManager + { + [CmifCommand(0)] + public Result OpenHardwareOpusDecoder( + out IHardwareOpusDecoder decoder, + HardwareOpusDecoderParameterInternal parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle); + + return Result.Success; + } + + [CmifCommand(1)] + public Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter) + { + size = 0; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = GetOpusDecoderSize(parameter.ChannelsCount); + + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64); + size = opusDecoderSize + 1536 + frameSize; + + return Result.Success; + } + + [CmifCommand(2)] // 3.0.0+ + public Result OpenHardwareOpusDecoderForMultiStream( + out IHardwareOpusDecoder decoder, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidNumberOfStreams; + } + + decoder = new HardwareOpusDecoder( + parameter.SampleRate, + parameter.ChannelsCount, + parameter.NumberOfStreams, + parameter.NumberOfStereoStreams, + parameter.ChannelMappings.AsSpan().ToArray(), + workBufferHandle); + + return Result.Success; + } + + [CmifCommand(3)] // 3.0.0+ + public Result GetWorkBufferSizeForMultiStream( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x110)] in HardwareOpusMultiStreamDecoderParameterInternal parameter) + { + size = 0; + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams); + + int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64); + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * 1920 / sampleRateRatio : 0, 64); + size = opusDecoderSize + streamSize + frameSize; + + return Result.Success; + } + + [CmifCommand(4)] // 12.0.0+ + public Result OpenHardwareOpusDecoderEx( + out IHardwareOpusDecoder decoder, + HardwareOpusDecoderParameterInternalEx parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + decoder = new HardwareOpusDecoder(parameter.SampleRate, parameter.ChannelsCount, workBufferHandle); + + return Result.Success; + } + + [CmifCommand(5)] // 12.0.0+ + public Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: false); + } + + [CmifCommand(6)] // 12.0.0+ + public Result OpenHardwareOpusDecoderForMultiStreamEx( + out IHardwareOpusDecoder decoder, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, + [CopyHandle] int workBufferHandle, + int workBufferSize) + { + decoder = null; + + if (!IsValidSampleRate(parameter.SampleRate)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidSampleRate; + } + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidChannelCount; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + HorizonStatic.Syscall.CloseHandle(workBufferHandle); + + return CodecResult.InvalidNumberOfStreams; + } + + decoder = new HardwareOpusDecoder( + parameter.SampleRate, + parameter.ChannelsCount, + parameter.NumberOfStreams, + parameter.NumberOfStereoStreams, + parameter.ChannelMappings.AsSpan().ToArray(), + workBufferHandle); + + return Result.Success; + } + + [CmifCommand(7)] // 12.0.0+ + public Result GetWorkBufferSizeForMultiStreamEx( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: false); + } + + [CmifCommand(8)] // 16.0.0+ + public Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeExImpl(out size, in parameter, fromDsp: true); + } + + [CmifCommand(9)] // 16.0.0+ + public Result GetWorkBufferSizeForMultiStreamExEx( + out int size, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.Pointer, 0x118)] in HardwareOpusMultiStreamDecoderParameterInternalEx parameter) + { + return GetWorkBufferSizeForMultiStreamExImpl(out size, in parameter, fromDsp: true); + } + + private Result GetWorkBufferSizeExImpl(out int size, in HardwareOpusDecoderParameterInternalEx parameter, bool fromDsp) + { + size = 0; + + if (!IsValidChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = fromDsp ? GetDspOpusDecoderSize(parameter.ChannelsCount) : GetOpusDecoderSize(parameter.ChannelsCount); + + int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64); + size = opusDecoderSize + 1536 + frameSize; + + return Result.Success; + } + + private Result GetWorkBufferSizeForMultiStreamExImpl(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, bool fromDsp) + { + size = 0; + + if (!IsValidMultiChannelCount(parameter.ChannelsCount)) + { + return CodecResult.InvalidChannelCount; + } + + if (!IsValidSampleRate(parameter.SampleRate)) + { + return CodecResult.InvalidSampleRate; + } + + if (!IsValidNumberOfStreams(parameter.NumberOfStreams, parameter.NumberOfStereoStreams, parameter.ChannelsCount)) + { + return CodecResult.InvalidSampleRate; + } + + int opusDecoderSize = fromDsp + ? GetDspOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams) + : GetOpusMultistreamDecoderSize(parameter.NumberOfStreams, parameter.NumberOfStereoStreams); + + int frameSizeMono48KHz = parameter.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920; + int streamSize = BitUtils.AlignUp(parameter.NumberOfStreams * 1500, 64); + int sampleRateRatio = parameter.SampleRate != 0 ? 48000 / parameter.SampleRate : 0; + int frameSize = BitUtils.AlignUp(sampleRateRatio != 0 ? parameter.ChannelsCount * frameSizeMono48KHz / sampleRateRatio : 0, 64); + size = opusDecoderSize + streamSize + frameSize; + + return Result.Success; + } + + private static int GetDspOpusDecoderSize(int channelsCount) + { + // TODO: Figure out the size returned here. + // Not really important because we don't use the work buffer, and the size being lower is fine. + + return 0; + } + + private static int GetDspOpusMultistreamDecoderSize(int streams, int coupledStreams) + { + // TODO: Figure out the size returned here. + // Not really important because we don't use the work buffer, and the size being lower is fine. + + return 0; + } + + 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) | 0x50; + + return opusDecoderSize + SilkDecoderSize + celtDecoderSize; + } + + 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 + 0xb920; + } + + private static int Align4(int value) + { + return BitUtils.AlignUp(value, 4); + } + + private static int GetOpusDecoderAllocSize(int channelsCount) + { + return channelsCount * 0x800 + 0x4800; + } + + 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 + 0x54; + } + + private static bool IsValidChannelCount(int channelsCount) + { + return channelsCount > 0 && channelsCount <= 2; + } + + private static bool IsValidMultiChannelCount(int channelsCount) + { + return channelsCount > 0 && channelsCount <= 255; + } + + private static bool IsValidSampleRate(int sampleRate) + { + switch (sampleRate) + { + case 8000: + case 12000: + case 16000: + case 24000: + case 48000: + return true; + } + + return false; + } + + private static bool IsValidNumberOfStreams(int numberOfStreams, int numberOfStereoStreams, int channelsCount) + { + return numberOfStreams > 0 && + numberOfStreams + numberOfStereoStreams <= channelsCount && + numberOfStereoStreams >= 0 && + numberOfStereoStreams <= numberOfStreams; + } + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs new file mode 100644 index 00000000..271a592c --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternal.cs @@ -0,0 +1,11 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)] + struct HardwareOpusDecoderParameterInternal + { + public int SampleRate; + public int ChannelsCount; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs new file mode 100644 index 00000000..e2b81c77 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoderParameterInternalEx.cs @@ -0,0 +1,13 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)] + struct HardwareOpusDecoderParameterInternalEx + { + public int SampleRate; + public int ChannelsCount; + public OpusDecoderFlags Flags; + public uint Reserved; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs new file mode 100644 index 00000000..98536a4f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternal.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x110)] + struct HardwareOpusMultiStreamDecoderParameterInternal + { + public int SampleRate; + public int ChannelsCount; + public int NumberOfStreams; + public int NumberOfStereoStreams; + public Array256<byte> ChannelMappings; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs new file mode 100644 index 00000000..8f8615df --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusMultiStreamDecoderParameterInternalEx.cs @@ -0,0 +1,17 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [StructLayout(LayoutKind.Sequential, Size = 0x118)] + struct HardwareOpusMultiStreamDecoderParameterInternalEx + { + public int SampleRate; + public int ChannelsCount; + public int NumberOfStreams; + public int NumberOfStereoStreams; + public OpusDecoderFlags Flags; + public uint Reserved; + public Array256<byte> ChannelMappings; + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs new file mode 100644 index 00000000..ae09ad15 --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs @@ -0,0 +1,20 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; +using System; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + interface IHardwareOpusDecoder : IServiceObject + { + Result DecodeInterleavedOld(out int outConsumed, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input); + Result SetContext(ReadOnlySpan<byte> context); + Result DecodeInterleavedForMultiStreamOld(out int outConsumed, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input); + Result SetContextForMultiStream(ReadOnlySpan<byte> context); + Result DecodeInterleavedWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input); + Result DecodeInterleavedForMultiStreamWithPerfOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input); + Result DecodeInterleavedWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset); + Result DecodeInterleavedForMultiStreamWithPerfAndResetOld(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset); + Result DecodeInterleaved(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset); + Result DecodeInterleavedForMultiStream(out int outConsumed, out long timeTaken, out int outSamples, Span<byte> output, ReadOnlySpan<byte> input, bool reset); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs new file mode 100644 index 00000000..fb6c787b --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoderManager.cs @@ -0,0 +1,19 @@ +using Ryujinx.Horizon.Common; +using Ryujinx.Horizon.Sdk.Sf; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + interface IHardwareOpusDecoderManager : IServiceObject + { + Result OpenHardwareOpusDecoder(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSize(out int size, HardwareOpusDecoderParameterInternal parameter); + Result OpenHardwareOpusDecoderForMultiStream(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternal parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeForMultiStream(out int size, in HardwareOpusMultiStreamDecoderParameterInternal parameter); + Result OpenHardwareOpusDecoderEx(out IHardwareOpusDecoder decoder, HardwareOpusDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeEx(out int size, HardwareOpusDecoderParameterInternalEx parameter); + Result OpenHardwareOpusDecoderForMultiStreamEx(out IHardwareOpusDecoder decoder, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter, int workBufferHandle, int workBufferSize); + Result GetWorkBufferSizeForMultiStreamEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter); + Result GetWorkBufferSizeExEx(out int size, HardwareOpusDecoderParameterInternalEx parameter); + Result GetWorkBufferSizeForMultiStreamExEx(out int size, in HardwareOpusMultiStreamDecoderParameterInternalEx parameter); + } +} diff --git a/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs new file mode 100644 index 00000000..d630b10f --- /dev/null +++ b/src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.Horizon.Sdk.Codec.Detail +{ + [Flags] + enum OpusDecoderFlags : uint + { + None, + LargeFrameSize = 1 << 0, + } +} |
