mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2025-06-30 00:20:46 -07:00
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:
@ -56,6 +56,7 @@ namespace Ryujinx.Horizon.Arp
|
||||
{
|
||||
_applicationInstanceManager.Dispose();
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
17
src/Ryujinx.Horizon/Audio/AudioMain.cs
Normal file
17
src/Ryujinx.Horizon/Audio/AudioMain.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
78
src/Ryujinx.Horizon/Audio/AudioManagers.cs
Normal file
78
src/Ryujinx.Horizon/Audio/AudioManagers.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
55
src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs
Normal file
55
src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
46
src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs
Normal file
46
src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
17
src/Ryujinx.Horizon/Audio/HwopusMain.cs
Normal file
17
src/Ryujinx.Horizon/Audio/HwopusMain.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -44,6 +44,7 @@ namespace Ryujinx.Horizon.Bcat
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ namespace Ryujinx.Horizon.Friends
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ namespace Ryujinx.Horizon.Hshl
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,7 @@ namespace Ryujinx.Horizon.Ins
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.Lbl
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.LogManager
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.MmNv
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,7 @@ namespace Ryujinx.Horizon.Ovln
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ namespace Ryujinx.Horizon.Prepo
|
||||
{
|
||||
_arp.Dispose();
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,6 +45,7 @@ namespace Ryujinx.Horizon.Psc
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
|
71
src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs
Normal file
71
src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs
Normal 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,
|
||||
}
|
||||
}
|
15
src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs
Normal file
15
src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
50
src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs
Normal file
50
src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
Normal 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);
|
||||
}
|
||||
}
|
252
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
Normal file
252
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
171
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs
Normal file
171
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
130
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs
Normal file
130
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
23
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs
Normal file
23
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||
{
|
||||
enum AudioInProtocolName : byte
|
||||
{
|
||||
DeviceIn = 0,
|
||||
UacIn = 1,
|
||||
}
|
||||
}
|
154
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs
Normal file
154
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
93
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs
Normal file
93
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
187
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs
Normal file
187
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
132
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs
Normal file
132
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
30
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs
Normal file
30
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioSnoopManager.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
30
src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs
Normal file
30
src/Ryujinx.Horizon/Sdk/Audio/Detail/DeviceName.cs
Normal 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]);
|
||||
}
|
||||
}
|
||||
}
|
147
src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs
Normal file
147
src/Ryujinx.Horizon/Sdk/Audio/Detail/FinalOutputRecorder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
24
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs
Normal file
24
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioDevice.cs
Normal 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);
|
||||
}
|
||||
}
|
26
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs
Normal file
26
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioIn.cs
Normal 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);
|
||||
}
|
||||
}
|
43
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs
Normal file
43
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioInManager.cs
Normal 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);
|
||||
}
|
||||
}
|
25
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs
Normal file
25
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOut.cs
Normal 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);
|
||||
}
|
||||
}
|
32
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs
Normal file
32
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioOutManager.cs
Normal 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);
|
||||
}
|
||||
}
|
24
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs
Normal file
24
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
12
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioSnoopManager.cs
Normal 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);
|
||||
}
|
||||
}
|
22
src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs
Normal file
22
src/Ryujinx.Horizon/Sdk/Audio/Detail/IFinalOutputRecorder.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
16
src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs
Normal file
16
src/Ryujinx.Horizon/Sdk/Codec/CodecResult.cs
Normal 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);
|
||||
}
|
||||
}
|
336
src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
Normal file
336
src/Ryujinx.Horizon/Sdk/Codec/Detail/HardwareOpusDecoder.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
20
src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs
Normal file
20
src/Ryujinx.Horizon/Sdk/Codec/Detail/IHardwareOpusDecoder.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
11
src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
Normal file
11
src/Ryujinx.Horizon/Sdk/Codec/Detail/OpusDecoderFlags.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Horizon.Sdk.Codec.Detail
|
||||
{
|
||||
[Flags]
|
||||
enum OpusDecoderFlags : uint
|
||||
{
|
||||
None,
|
||||
LargeFrameSize = 1 << 0,
|
||||
}
|
||||
}
|
@ -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>();
|
||||
|
@ -41,6 +41,7 @@ namespace Ryujinx.Horizon.Srepo
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ namespace Ryujinx.Horizon.Usb
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,6 +54,7 @@ namespace Ryujinx.Horizon.Wlan
|
||||
public void Shutdown()
|
||||
{
|
||||
_serverManager.Dispose();
|
||||
_sm.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user