Migrate Audio service to new IPC (#6285)

* Migrate audren to new IPC

* Migrate audout

* Migrate audin

* Migrate hwopus

* Bye bye old audio service

* Switch volume control to IHardwareDeviceDriver

* Somewhat unrelated changes

* Remove Concentus reference from HLE

* Implement OpenAudioRendererForManualExecution

* Remove SetVolume/GetVolume methods that are not necessary

* Remove SetVolume/GetVolume methods that are not necessary (2)

* Fix incorrect volume update

* PR feedback

* PR feedback

* Stub audrec

* Init outParameter

* Make FinalOutputRecorderParameter/Internal readonly

* Make FinalOutputRecorder IDisposable

* Fix HardwareOpusDecoderManager parameter buffers

* Opus work buffer size and error handling improvements

* Add AudioInProtocolName enum

* Fix potential divisions by zero
This commit is contained in:
gdkchan
2024-02-22 16:58:33 -03:00
committed by GitHub
parent 57d8afd0c9
commit d4d0a48bfe
130 changed files with 3096 additions and 3174 deletions

View File

@ -56,6 +56,7 @@ namespace Ryujinx.Horizon.Arp
{
_applicationInstanceManager.Dispose();
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -0,0 +1,17 @@
namespace Ryujinx.Horizon.Audio
{
class AudioMain : IService
{
public static void Main(ServiceTable serviceTable)
{
AudioUserIpcServer ipcServer = new();
ipcServer.Initialize();
serviceTable.SignalServiceReady();
ipcServer.ServiceRequests();
ipcServer.Shutdown();
}
}
}

View File

@ -0,0 +1,78 @@
using Ryujinx.Audio;
using Ryujinx.Audio.Input;
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Output;
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Cpu;
using Ryujinx.Horizon.Sdk.Audio;
using System;
namespace Ryujinx.Horizon.Audio
{
class AudioManagers : IDisposable
{
public AudioManager AudioManager { get; }
public AudioOutputManager AudioOutputManager { get; }
public AudioInputManager AudioInputManager { get; }
public AudioRendererManager AudioRendererManager { get; }
public VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; }
public AudioManagers(IHardwareDeviceDriver audioDeviceDriver, ITickSource tickSource)
{
AudioManager = new AudioManager();
AudioOutputManager = new AudioOutputManager();
AudioInputManager = new AudioInputManager();
AudioRendererManager = new AudioRendererManager(tickSource);
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(audioDeviceDriver);
IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++)
{
audioOutputRegisterBufferEvents[i] = new AudioEvent();
}
AudioOutputManager.Initialize(audioDeviceDriver, audioOutputRegisterBufferEvents);
IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++)
{
audioInputRegisterBufferEvents[i] = new AudioEvent();
}
AudioInputManager.Initialize(audioDeviceDriver, audioInputRegisterBufferEvents);
IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax];
for (int i = 0; i < systemEvents.Length; i++)
{
systemEvents[i] = new AudioEvent();
}
AudioManager.Initialize(audioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update);
AudioRendererManager.Initialize(systemEvents, audioDeviceDriver);
AudioManager.Start();
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
AudioManager.Dispose();
AudioOutputManager.Dispose();
AudioInputManager.Dispose();
AudioRendererManager.Dispose();
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@ -0,0 +1,55 @@
using Ryujinx.Horizon.Sdk.Audio.Detail;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
namespace Ryujinx.Horizon.Audio
{
class AudioUserIpcServer
{
private const int MaxSessionsCount = 30;
private const int PointerBufferSize = 0xB40;
private const int MaxDomains = 0;
private const int MaxDomainObjects = 0;
private const int MaxPortsCount = 1;
private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private ServerManager _serverManager;
private AudioManagers _managers;
public void Initialize()
{
HeapAllocator allocator = new();
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount);
_managers = new AudioManagers(HorizonStatic.Options.AudioDeviceDriver, HorizonStatic.Options.TickSource);
AudioRendererManager audioRendererManager = new(_managers.AudioRendererManager, _managers.AudioDeviceSessionRegistry);
AudioOutManager audioOutManager = new(_managers.AudioOutputManager);
AudioInManager audioInManager = new(_managers.AudioInputManager);
FinalOutputRecorderManager finalOutputRecorderManager = new();
_serverManager.RegisterObjectForServer(audioRendererManager, ServiceName.Encode("audren:u"), MaxSessionsCount);
_serverManager.RegisterObjectForServer(audioOutManager, ServiceName.Encode("audout:u"), MaxSessionsCount);
_serverManager.RegisterObjectForServer(audioInManager, ServiceName.Encode("audin:u"), MaxSessionsCount);
_serverManager.RegisterObjectForServer(finalOutputRecorderManager, ServiceName.Encode("audrec:u"), MaxSessionsCount);
}
public void ServiceRequests()
{
_serverManager.ServiceRequests();
}
public void Shutdown()
{
_serverManager.Dispose();
_managers.Dispose();
_sm.Dispose();
}
}
}

View File

@ -0,0 +1,46 @@
using Ryujinx.Horizon.Sdk.Codec.Detail;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
namespace Ryujinx.Horizon.Audio
{
class HwopusIpcServer
{
private const int MaxSessionsCount = 24;
private const int PointerBufferSize = 0x1000;
private const int MaxDomains = 8;
private const int MaxDomainObjects = 256;
private const int MaxPortsCount = 1;
private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
private SmApi _sm;
private ServerManager _serverManager;
public void Initialize()
{
HeapAllocator allocator = new();
_sm = new SmApi();
_sm.Initialize().AbortOnFailure();
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount);
HardwareOpusDecoderManager hardwareOpusDecoderManager = new();
_serverManager.RegisterObjectForServer(hardwareOpusDecoderManager, ServiceName.Encode("hwopus"), MaxSessionsCount);
}
public void ServiceRequests()
{
_serverManager.ServiceRequests();
}
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -0,0 +1,17 @@
namespace Ryujinx.Horizon.Audio
{
class HwopusMain : IService
{
public static void Main(ServiceTable serviceTable)
{
HwopusIpcServer ipcServer = new();
ipcServer.Initialize();
serviceTable.SignalServiceReady();
ipcServer.ServiceRequests();
ipcServer.Shutdown();
}
}
}

View File

@ -44,6 +44,7 @@ namespace Ryujinx.Horizon.Bcat
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -44,6 +44,7 @@ namespace Ryujinx.Horizon.Friends
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -1,4 +1,6 @@
using LibHac;
using Ryujinx.Audio.Integration;
using Ryujinx.Cpu;
using Ryujinx.Horizon.Sdk.Account;
using Ryujinx.Horizon.Sdk.Fs;
@ -12,14 +14,24 @@ namespace Ryujinx.Horizon
public HorizonClient BcatClient { get; }
public IFsClient FsClient { get; }
public IEmulatorAccountManager AccountManager { get; }
public IHardwareDeviceDriver AudioDeviceDriver { get; }
public ITickSource TickSource { get; }
public HorizonOptions(bool ignoreMissingServices, HorizonClient bcatClient, IFsClient fsClient, IEmulatorAccountManager accountManager)
public HorizonOptions(
bool ignoreMissingServices,
HorizonClient bcatClient,
IFsClient fsClient,
IEmulatorAccountManager accountManager,
IHardwareDeviceDriver audioDeviceDriver,
ITickSource tickSource)
{
IgnoreMissingServices = ignoreMissingServices;
ThrowOnInvalidCommandIds = true;
BcatClient = bcatClient;
FsClient = fsClient;
AccountManager = accountManager;
AudioDeviceDriver = audioDeviceDriver;
TickSource = tickSource;
}
}
}

View File

@ -42,6 +42,7 @@ namespace Ryujinx.Horizon.Hshl
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -42,6 +42,7 @@ namespace Ryujinx.Horizon.Ins
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.Lbl
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.LogManager
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.MmNv
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -3,7 +3,6 @@ using Ryujinx.Horizon.Sdk.Fs;
using Ryujinx.Horizon.Sdk.Ngc.Detail;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using Ryujinx.Horizon.Sdk.Sm;
using System;
namespace Ryujinx.Horizon.Ngc
{
@ -46,6 +45,7 @@ namespace Ryujinx.Horizon.Ngc
{
_serverManager.Dispose();
_profanityFilter.Dispose();
_sm.Dispose();
}
}
}

View File

@ -43,6 +43,7 @@ namespace Ryujinx.Horizon.Ovln
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -51,6 +51,7 @@ namespace Ryujinx.Horizon.Prepo
{
_arp.Dispose();
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -45,6 +45,7 @@ namespace Ryujinx.Horizon.Psc
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -5,6 +5,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" />
<ProjectReference Include="..\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
@ -12,7 +13,13 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Concentus" />
<PackageReference Include="LibHac" />
</ItemGroup>
<!-- Due to Concentus. -->
<PropertyGroup>
<NoWarn>NU1605</NoWarn>
</PropertyGroup>
</Project>

View File

@ -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;

View File

@ -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,
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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();
}
}
}

View File

@ -0,0 +1,8 @@
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
enum AudioInProtocolName : byte
{
DeviceIn = 0,
UacIn = 1,
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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]);
}
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace Ryujinx.Horizon.Sdk.Codec.Detail
{
[Flags]
enum OpusDecoderFlags : uint
{
None,
LargeFrameSize = 1 << 0,
}
}

View File

@ -1,4 +1,5 @@
using Ryujinx.Horizon.Arp;
using Ryujinx.Horizon.Audio;
using Ryujinx.Horizon.Bcat;
using Ryujinx.Horizon.Friends;
using Ryujinx.Horizon.Hshl;
@ -39,9 +40,11 @@ namespace Ryujinx.Horizon
}
RegisterService<ArpMain>();
RegisterService<AudioMain>();
RegisterService<BcatMain>();
RegisterService<FriendsMain>();
RegisterService<HshlMain>();
RegisterService<HwopusMain>(); // TODO: Merge with audio once we can start multiple threads.
RegisterService<InsMain>();
RegisterService<LblMain>();
RegisterService<LmMain>();

View File

@ -41,6 +41,7 @@ namespace Ryujinx.Horizon.Srepo
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -66,6 +66,7 @@ namespace Ryujinx.Horizon.Usb
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}

View File

@ -54,6 +54,7 @@ namespace Ryujinx.Horizon.Wlan
public void Shutdown()
{
_serverManager.Dispose();
_sm.Dispose();
}
}
}