0
0
Fork 0
mirror of https://github.com/ryujinx-mirror/ryujinx.git synced 2024-12-22 22:45:48 +00: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:
gdkchan 2024-02-22 16:58:33 -03:00 committed by GitHub
parent 57d8afd0c9
commit d4d0a48bfe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
130 changed files with 3096 additions and 3174 deletions

View file

@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
private bool _stillRunning;
private readonly Thread _updaterThread;
private float _volume;
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
foreach (OpenALHardwareDeviceSession session in _sessions.Keys)
{
session.UpdateMasterVolume(value);
}
}
}
public OpenALHardwareDeviceDriver()
{
_device = ALC.OpenDevice("");
@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
Name = "HardwareDeviceDriver.OpenAL",
};
_volume = 1f;
_updaterThread.Start();
}
@ -52,7 +73,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
}
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
throw new ArgumentException($"{channelCount}");
}
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
OpenALHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);

View file

@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
private bool _isActive;
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
private ulong _playedSampleCount;
private float _volume;
private readonly object _lock = new();
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_queuedBuffers = new Queue<OpenALAudioBuffer>();
@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
_targetFormat = GetALFormat();
_isActive = false;
_playedSampleCount = 0;
SetVolume(requestedVolume);
SetVolume(1f);
}
private ALFormat GetALFormat()
@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
public override void SetVolume(float volume)
{
lock (_lock)
{
AL.Source(_sourceId, ALSourcef.Gain, volume);
}
_volume = volume;
UpdateMasterVolume(_driver.Volume);
}
public override float GetVolume()
{
AL.GetSource(_sourceId, ALSourcef.Gain, out float volume);
return _volume;
}
return volume;
public void UpdateMasterVolume(float newVolume)
{
lock (_lock)
{
AL.Source(_sourceId, ALSourcef.Gain, newVolume * _volume);
}
}
public override void Start()

View file

@ -20,6 +20,8 @@ namespace Ryujinx.Audio.Backends.SDL2
private readonly bool _supportSurroundConfiguration;
public float Volume { get; set; }
// TODO: Add this to SDL2-CS
// NOTE: We use a DllImport here because of marshaling issue for spec.
#pragma warning disable SYSLIB1054
@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
{
_supportSurroundConfiguration = spec.channels >= 6;
}
Volume = 1f;
}
public static bool IsSupported => IsSupportedInternal();
@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
return _pauseEvent;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
}
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
SDL2HardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);

View file

@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
private float _volume;
private readonly ushort _nativeSampleFormat;
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public SDL2HardwareDeviceSession(SDL2HardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
@ -37,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
_sampleCount = uint.MaxValue;
_started = false;
_volume = requestedVolume;
_volume = 1f;
}
private void EnsureAudioStreamSetup(AudioBuffer buffer)
@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Backends.SDL2
streamSpan.Clear();
// Apply volume to written data
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_volume * SDL_MIX_MAXVOLUME));
SDL_MixAudioFormat(stream, pStreamSrc, _nativeSampleFormat, (uint)samples.Length, (int)(_driver.Volume * _volume * SDL_MIX_MAXVOLUME));
}
ulong sampleCount = GetSampleCount(samples.Length);

View file

@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
private int _disposeState;
private float _volume = 1f;
public float Volume
{
get
{
return _volume;
}
set
{
_volume = value;
foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
{
session.UpdateMasterVolume(value);
}
}
}
public SoundIoHardwareDeviceDriver()
{
_audioContext = SoundIoContext.Create();
@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
return _pauseEvent;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
sampleRate = Constants.TargetSampleRate;
}
volume = Math.Clamp(volume, 0, 1);
if (direction != Direction.Output)
{
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
}
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
SoundIoHardwareDeviceSession session = new(this, memoryManager, sampleFormat, sampleRate, channelCount);
_sessions.TryAdd(session, 0);

View file

@ -18,16 +18,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
private readonly DynamicRingBuffer _ringBuffer;
private ulong _playedSampleCount;
private readonly ManualResetEvent _updateRequiredEvent;
private float _volume;
private int _disposeState;
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_driver = driver;
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
_ringBuffer = new DynamicRingBuffer();
_volume = 1f;
SetupOutputStream(requestedVolume);
SetupOutputStream(driver.Volume);
}
private void SetupOutputStream(float requestedVolume)
@ -47,7 +49,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override float GetVolume()
{
return _outputStream.Volume;
return _volume;
}
public override void PrepareToClose() { }
@ -63,7 +65,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
public override void SetVolume(float volume)
{
_outputStream.SetVolume(volume);
_volume = volume;
_outputStream.SetVolume(_driver.Volume * volume);
}
public void UpdateMasterVolume(float newVolume)
{
_outputStream.SetVolume(newVolume * _volume);
}
public override void Start()

View file

@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
public static bool IsSupported => true;
public float Volume
{
get => _realDriver.Volume;
set => _realDriver.Volume = value;
}
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
{
_realDriver = realDevice;
@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
throw new ArgumentException("No valid sample format configuration found!");
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (channelCount == 0)
{
@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
sampleRate = Constants.TargetSampleRate;
}
volume = Math.Clamp(volume, 0, 1);
if (!_realDriver.SupportsDirection(direction))
{
if (direction == Direction.Input)
@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount, volume);
IHardwareDeviceSession realSession = _realDriver.OpenDeviceSession(direction, memoryManager, hardwareSampleFormat, sampleRate, hardwareChannelCount);
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
{

View file

@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
public static bool IsSupported => true;
public float Volume { get; set; }
public DummyHardwareDeviceDriver()
{
_updateRequiredEvent = new ManualResetEvent(false);
_pauseEvent = new ManualResetEvent(true);
Volume = 1f;
}
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount)
{
if (sampleRate == 0)
{
@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
if (direction == Direction.Output)
{
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
return new DummyHardwareDeviceSessionOutput(this, memoryManager, sampleFormat, sampleRate, channelCount);
}
return new DummyHardwareDeviceSessionInput(this, memoryManager);

View file

@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
private ulong _playedSampleCount;
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
public DummyHardwareDeviceSessionOutput(IHardwareDeviceDriver manager, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
{
_volume = requestedVolume;
_volume = 1f;
_manager = manager;
}

View file

@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
/// </summary>
/// <param name="filtered">If true, filter disconnected devices</param>
/// <returns>The list of all audio inputs name</returns>
#pragma warning disable CA1822 // Mark member as static
public string[] ListAudioIns(bool filtered)
{
if (filtered)
@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
return new[] { Constants.DefaultDeviceInputName };
}
#pragma warning restore CA1822
/// <summary>
/// Open a new <see cref="AudioInputSystem"/>.
@ -188,8 +186,6 @@ namespace Ryujinx.Audio.Input
/// <param name="inputDeviceName">The input device name wanted by the user</param>
/// <param name="sampleFormat">The sample format to use</param>
/// <param name="parameter">The user configuration</param>
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
/// <param name="processHandle">The process handle of the application</param>
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
public ResultCode OpenAudioIn(out string outputDeviceName,
out AudioOutputConfiguration outputConfiguration,
@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
IVirtualMemoryManager memoryManager,
string inputDeviceName,
SampleFormat sampleFormat,
ref AudioInputConfiguration parameter,
ulong appletResourceUserId,
uint processHandle)
ref AudioInputConfiguration parameter)
{
int sessionId = AcquireSessionId();

View file

@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
private readonly byte[] _buffer;
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate, float volume)
public HardwareDeviceImpl(IHardwareDeviceDriver deviceDriver, uint channelCount, uint sampleRate)
{
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount, volume);
_session = deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, null, SampleFormat.PcmInt16, sampleRate, channelCount);
_channelCount = channelCount;
_sampleRate = sampleRate;
_currentBufferTag = 0;

View file

@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
Output,
}
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume = 1f);
float Volume { get; set; }
IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount);
ManualResetEvent GetUpdateRequiredEvent();
ManualResetEvent GetPauseEvent();

View file

@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
/// Get the list of all audio outputs name.
/// </summary>
/// <returns>The list of all audio outputs name</returns>
#pragma warning disable CA1822 // Mark member as static
public string[] ListAudioOuts()
{
return new[] { Constants.DefaultDeviceOutputName };
}
#pragma warning restore CA1822
/// <summary>
/// Open a new <see cref="AudioOutputSystem"/>.
@ -182,9 +180,6 @@ namespace Ryujinx.Audio.Output
/// <param name="inputDeviceName">The input device name wanted by the user</param>
/// <param name="sampleFormat">The sample format to use</param>
/// <param name="parameter">The user configuration</param>
/// <param name="appletResourceUserId">The applet resource user id of the application</param>
/// <param name="processHandle">The process handle of the application</param>
/// <param name="volume">The volume level to request</param>
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
public ResultCode OpenAudioOut(out string outputDeviceName,
out AudioOutputConfiguration outputConfiguration,
@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
IVirtualMemoryManager memoryManager,
string inputDeviceName,
SampleFormat sampleFormat,
ref AudioInputConfiguration parameter,
ulong appletResourceUserId,
uint processHandle,
float volume)
ref AudioInputConfiguration parameter)
{
int sessionId = AcquireSessionId();
_sessionsBufferEvents[sessionId].Clear();
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount, volume);
IHardwareDeviceSession deviceSession = _deviceDriver.OpenDeviceSession(IHardwareDeviceDriver.Direction.Output, memoryManager, sampleFormat, parameter.SampleRate, parameter.ChannelCount);
AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
return result;
}
/// <summary>
/// Sets the volume for all output devices.
/// </summary>
/// <param name="volume">The volume to set.</param>
public void SetVolume(float volume)
{
if (_sessions != null)
{
foreach (AudioOutputSystem session in _sessions)
{
session?.SetVolume(volume);
}
}
}
/// <summary>
/// Gets the volume for all output devices.
/// </summary>
/// <returns>A float indicating the volume level.</returns>
public float GetVolume()
{
if (_sessions != null)
{
foreach (AudioOutputSystem session in _sessions)
{
if (session != null)
{
return session.GetVolume();
}
}
}
return 0.0f;
}
public void Dispose()
{
GC.SuppressFinalize(this);

View file

@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
_event = new ManualResetEvent(false);
}
#pragma warning disable IDE0051 // Remove unused private member
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
{
// Get the real device driver (In case the compat layer is on top of it).
@ -59,9 +58,8 @@ namespace Ryujinx.Audio.Renderer.Dsp
// NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
return 2;
}
#pragma warning restore IDE0051
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
public void Start(IHardwareDeviceDriver deviceDriver)
{
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
@ -70,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
for (int i = 0; i < OutputDevices.Length; i++)
{
// TODO: Don't hardcode sample rate.
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate, volume);
OutputDevices[i] = new HardwareDeviceImpl(deviceDriver, channelCount, Constants.TargetSampleRate);
}
_mailbox = new Mailbox<MailboxMessage>();
@ -231,33 +229,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
_mailbox.SendResponse(MailboxMessage.Stop);
}
public float GetVolume()
{
if (OutputDevices != null)
{
foreach (IHardwareDevice outputDevice in OutputDevices)
{
if (outputDevice != null)
{
return outputDevice.GetVolume();
}
}
}
return 0f;
}
public void SetVolume(float volume)
{
if (OutputDevices != null)
{
foreach (IHardwareDevice outputDevice in OutputDevices)
{
outputDevice?.SetVolume(volume);
}
}
}
public void Dispose()
{
GC.SuppressFinalize(this);
@ -269,6 +240,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
if (disposing)
{
_event.Dispose();
_mailbox?.Dispose();
}
}
}

View file

@ -177,12 +177,12 @@ namespace Ryujinx.Audio.Renderer.Server
/// <summary>
/// Start the <see cref="AudioProcessor"/> and worker thread.
/// </summary>
private void StartLocked(float volume)
private void StartLocked()
{
_isRunning = true;
// TODO: virtual device mapping (IAudioDevice)
Processor.Start(_deviceDriver, volume);
Processor.Start(_deviceDriver);
_workerThread = new Thread(SendCommands)
{
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server
/// Register a new <see cref="AudioRenderSystem"/>.
/// </summary>
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
private void Register(AudioRenderSystem renderer, float volume)
private void Register(AudioRenderSystem renderer)
{
lock (_sessionLock)
{
@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
if (!_isRunning)
{
StartLocked(volume);
StartLocked();
}
}
}
@ -312,8 +312,7 @@ namespace Ryujinx.Audio.Renderer.Server
ulong appletResourceUserId,
ulong workBufferAddress,
ulong workBufferSize,
uint processHandle,
float volume)
uint processHandle)
{
int sessionId = AcquireSessionId();
@ -338,7 +337,7 @@ namespace Ryujinx.Audio.Renderer.Server
{
renderer = audioRenderer;
Register(renderer, volume);
Register(renderer);
}
else
{
@ -350,21 +349,6 @@ namespace Ryujinx.Audio.Renderer.Server
return result;
}
public float GetVolume()
{
if (Processor != null)
{
return Processor.GetVolume();
}
return 0f;
}
public void SetVolume(float volume)
{
Processor?.SetVolume(volume);
}
public void Dispose()
{
GC.SuppressFinalize(this);

View file

@ -4,12 +4,6 @@ using LibHac.Fs;
using LibHac.Fs.Shim;
using LibHac.FsSystem;
using LibHac.Tools.FsSystem;
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.HLE.FileSystem;
using Ryujinx.HLE.HOS.Kernel;
@ -20,7 +14,6 @@ using Ryujinx.HLE.HOS.Services;
using Ryujinx.HLE.HOS.Services.Account.Acc;
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
using Ryujinx.HLE.HOS.Services.Apm;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using Ryujinx.HLE.HOS.Services.Caps;
using Ryujinx.HLE.HOS.Services.Mii;
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
@ -61,11 +54,6 @@ namespace Ryujinx.HLE.HOS
internal ITickSource TickSource { get; }
internal SurfaceFlinger SurfaceFlinger { get; private set; }
internal AudioManager AudioManager { get; private set; }
internal AudioOutputManager AudioOutputManager { get; private set; }
internal AudioInputManager AudioInputManager { get; private set; }
internal AudioRendererManager AudioRendererManager { get; private set; }
internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; }
public SystemStateMgr State { get; private set; }
@ -79,8 +67,6 @@ namespace Ryujinx.HLE.HOS
internal ServerBase SmServer { get; private set; }
internal ServerBase BsdServer { get; private set; }
internal ServerBase AudRenServer { get; private set; }
internal ServerBase AudOutServer { get; private set; }
internal ServerBase FsServer { get; private set; }
internal ServerBase HidServer { get; private set; }
internal ServerBase NvDrvServer { get; private set; }
@ -248,56 +234,6 @@ namespace Ryujinx.HLE.HOS
HostSyncpoint = new NvHostSyncpt(device);
SurfaceFlinger = new SurfaceFlinger(device);
InitializeAudioRenderer(TickSource);
}
private void InitializeAudioRenderer(ITickSource tickSource)
{
AudioManager = new AudioManager();
AudioOutputManager = new AudioOutputManager();
AudioInputManager = new AudioInputManager();
AudioRendererManager = new AudioRendererManager(tickSource);
AudioRendererManager.SetVolume(Device.Configuration.AudioVolume);
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(Device.AudioDeviceDriver);
IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++)
{
KEvent registerBufferEvent = new(KernelContext);
audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
}
AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents);
AudioOutputManager.SetVolume(Device.Configuration.AudioVolume);
IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++)
{
KEvent registerBufferEvent = new(KernelContext);
audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
}
AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents);
IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax];
for (int i = 0; i < systemEvents.Length; i++)
{
KEvent systemEvent = new(KernelContext);
systemEvents[i] = new AudioKernelEvent(systemEvent);
}
AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update);
AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver);
AudioManager.Start();
}
public void InitializeServices()
@ -310,8 +246,6 @@ namespace Ryujinx.HLE.HOS
SmServer.InitDone.WaitOne();
BsdServer = new ServerBase(KernelContext, "BsdServer");
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
FsServer = new ServerBase(KernelContext, "FsServer");
HidServer = new ServerBase(KernelContext, "HidServer");
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
@ -329,7 +263,13 @@ namespace Ryujinx.HLE.HOS
HorizonFsClient fsClient = new(this);
ServiceTable = new ServiceTable();
var services = ServiceTable.GetServices(new HorizonOptions(Device.Configuration.IgnoreMissingServices, LibHacHorizonManager.BcatClient, fsClient, AccountManager));
var services = ServiceTable.GetServices(new HorizonOptions
(Device.Configuration.IgnoreMissingServices,
LibHacHorizonManager.BcatClient,
fsClient,
AccountManager,
Device.AudioDeviceDriver,
TickSource));
foreach (var service in services)
{
@ -384,17 +324,6 @@ namespace Ryujinx.HLE.HOS
}
}
public void SetVolume(float volume)
{
AudioOutputManager.SetVolume(volume);
AudioRendererManager.SetVolume(volume);
}
public float GetVolume()
{
return AudioOutputManager.GetVolume() == 0 ? AudioRendererManager.GetVolume() : AudioOutputManager.GetVolume();
}
public void ReturnFocus()
{
AppletState.SetFocus(true);
@ -458,11 +387,7 @@ namespace Ryujinx.HLE.HOS
// "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
if (IsPaused)
{
AudioManager.StopUpdates();
TogglePauseEmulation(false);
AudioRendererManager.StopSendingCommands();
}
KProcess terminationProcess = new(KernelContext);
@ -513,12 +438,6 @@ namespace Ryujinx.HLE.HOS
// This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade.
INvDrvServices.Destroy();
AudioManager.Dispose();
AudioOutputManager.Dispose();
AudioInputManager.Dispose();
AudioRendererManager.Dispose();
if (LibHacHorizonManager.ApplicationClient != null)
{
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();

View file

@ -0,0 +1,25 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
{
readonly struct ExternalEvent : IExternalEvent
{
private readonly KWritableEvent _writableEvent;
public ExternalEvent(KWritableEvent writableEvent)
{
_writableEvent = writableEvent;
}
public readonly void Signal()
{
_writableEvent.Signal();
}
public readonly void Clear()
{
_writableEvent.Clear();
}
}
}

View file

@ -8,6 +8,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Kernel.Process;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
using System.Buffers;
using System.Threading;
@ -3142,6 +3143,37 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
}
#pragma warning restore CA1822
// Not actual syscalls, used by HLE services and such.
public IExternalEvent GetExternalEvent(int handle)
{
KWritableEvent writableEvent = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KWritableEvent>(handle);
if (writableEvent == null)
{
return null;
}
return new ExternalEvent(writableEvent);
}
public IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle)
{
return KernelStatic.GetCurrentProcess().HandleTable.GetKProcess(handle).CpuMemory;
}
public ulong GetTransferMemoryAddress(int handle)
{
KTransferMemory transferMemory = KernelStatic.GetCurrentProcess().HandleTable.GetObject<KTransferMemory>(handle);
if (transferMemory == null)
{
return 0;
}
return transferMemory.Address;
}
private static bool IsPointingInsideKernel(ulong address)
{
return (address + 0x1000000000) < 0xffffff000;

View file

@ -1,108 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Input;
using Ryujinx.Audio.Integration;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
{
class AudioIn : IAudioIn
{
private readonly AudioInputSystem _system;
private readonly uint _processHandle;
private readonly KernelContext _kernelContext;
public AudioIn(AudioInputSystem system, KernelContext kernelContext, uint processHandle)
{
_system = system;
_kernelContext = kernelContext;
_processHandle = processHandle;
}
public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer)
{
return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer);
}
public ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle)
{
return (ResultCode)_system.AppendUacBuffer(bufferTag, ref buffer, handle);
}
public bool ContainsBuffer(ulong bufferTag)
{
return _system.ContainsBuffer(bufferTag);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_system.Dispose();
_kernelContext.Syscall.CloseHandle((int)_processHandle);
}
}
public bool FlushBuffers()
{
return _system.FlushBuffers();
}
public uint GetBufferCount()
{
return _system.GetBufferCount();
}
public ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount)
{
return (ResultCode)_system.GetReleasedBuffers(releasedBuffers, out releasedCount);
}
public AudioDeviceState GetState()
{
return _system.GetState();
}
public float GetVolume()
{
return _system.GetVolume();
}
public KEvent RegisterBufferEvent()
{
IWritableEvent outEvent = _system.RegisterBufferEvent();
if (outEvent is AudioKernelEvent kernelEvent)
{
return kernelEvent.Event;
}
else
{
throw new NotImplementedException();
}
}
public void SetVolume(float volume)
{
_system.SetVolume(volume);
}
public ResultCode Start()
{
return (ResultCode)_system.Start();
}
public ResultCode Stop()
{
return (ResultCode)_system.Stop();
}
}
}

View file

@ -1,200 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
{
class AudioInServer : DisposableIpcService
{
private readonly IAudioIn _impl;
public AudioInServer(IAudioIn impl)
{
_impl = impl;
}
[CommandCmif(0)]
// GetAudioInState() -> u32 state
public ResultCode GetAudioInState(ServiceCtx context)
{
context.ResponseData.Write((uint)_impl.GetState());
return ResultCode.Success;
}
[CommandCmif(1)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return _impl.Start();
}
[CommandCmif(2)]
// Stop()
public ResultCode StopAudioIn(ServiceCtx context)
{
return _impl.Stop();
}
[CommandCmif(3)]
// AppendAudioInBuffer(u64 tag, buffer<nn::audio::AudioInBuffer, 5>)
public ResultCode AppendAudioInBuffer(ServiceCtx context)
{
ulong position = context.Request.SendBuff[0].Position;
ulong bufferTag = context.RequestData.ReadUInt64();
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendBuffer(bufferTag, ref data);
}
[CommandCmif(4)]
// RegisterBufferEvent() -> handle<copy>
public ResultCode RegisterBufferEvent(ServiceCtx context)
{
KEvent bufferEvent = _impl.RegisterBufferEvent();
if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
return ResultCode.Success;
}
[CommandCmif(5)]
// GetReleasedAudioInBuffers() -> (u32 count, buffer<u64, 6> tags)
public ResultCode GetReleasedAudioInBuffers(ServiceCtx context)
{
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
using WritableRegion outputRegion = context.Memory.GetWritableRegion((ulong)position, (int)size);
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
context.ResponseData.Write(releasedCount);
return result;
}
[CommandCmif(6)]
// ContainsAudioInBuffer(u64 tag) -> b8
public ResultCode ContainsAudioInBuffer(ServiceCtx context)
{
ulong bufferTag = context.RequestData.ReadUInt64();
context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
return ResultCode.Success;
}
[CommandCmif(7)] // 3.0.0+
// AppendUacInBuffer(u64 tag, handle<copy, unknown>, buffer<nn::audio::AudioInBuffer, 5>)
public ResultCode AppendUacInBuffer(ServiceCtx context)
{
ulong position = context.Request.SendBuff[0].Position;
ulong bufferTag = context.RequestData.ReadUInt64();
uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendUacBuffer(bufferTag, ref data, handle);
}
[CommandCmif(8)] // 3.0.0+
// AppendAudioInBufferAuto(u64 tag, buffer<nn::audio::AudioInBuffer, 0x21>)
public ResultCode AppendAudioInBufferAuto(ServiceCtx context)
{
(ulong position, _) = context.Request.GetBufferType0x21();
ulong bufferTag = context.RequestData.ReadUInt64();
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendBuffer(bufferTag, ref data);
}
[CommandCmif(9)] // 3.0.0+
// GetReleasedAudioInBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
public ResultCode GetReleasedAudioInBuffersAuto(ServiceCtx context)
{
(ulong position, ulong size) = context.Request.GetBufferType0x22();
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
context.ResponseData.Write(releasedCount);
return result;
}
[CommandCmif(10)] // 3.0.0+
// AppendUacInBufferAuto(u64 tag, handle<copy, event>, buffer<nn::audio::AudioInBuffer, 0x21>)
public ResultCode AppendUacInBufferAuto(ServiceCtx context)
{
(ulong position, _) = context.Request.GetBufferType0x21();
ulong bufferTag = context.RequestData.ReadUInt64();
uint handle = (uint)context.Request.HandleDesc.ToCopy[0];
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendUacBuffer(bufferTag, ref data, handle);
}
[CommandCmif(11)] // 4.0.0+
// GetAudioInBufferCount() -> u32
public ResultCode GetAudioInBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetBufferCount());
return ResultCode.Success;
}
[CommandCmif(12)] // 4.0.0+
// SetAudioInVolume(s32)
public ResultCode SetAudioInVolume(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
_impl.SetVolume(volume);
return ResultCode.Success;
}
[CommandCmif(13)] // 4.0.0+
// GetAudioInVolume() -> s32
public ResultCode GetAudioInVolume(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetVolume());
return ResultCode.Success;
}
[CommandCmif(14)] // 6.0.0+
// FlushAudioInBuffers() -> b8
public ResultCode FlushAudioInBuffers(ServiceCtx context)
{
context.ResponseData.Write(_impl.FlushBuffers());
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_impl.Dispose();
}
}
}
}

View file

@ -1,34 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn
{
interface IAudioIn : IDisposable
{
AudioDeviceState GetState();
ResultCode Start();
ResultCode Stop();
ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer);
// NOTE: This is broken by design... not quite sure what it's used for (if anything in production).
ResultCode AppendUacBuffer(ulong bufferTag, ref AudioUserBuffer buffer, uint handle);
KEvent RegisterBufferEvent();
ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount);
bool ContainsBuffer(ulong bufferTag);
uint GetBufferCount();
bool FlushBuffers();
void SetVolume(float volume);
float GetVolume();
}
}

View file

@ -1,40 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Input;
using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
using AudioInManagerImpl = Ryujinx.Audio.Input.AudioInputManager;
namespace Ryujinx.HLE.HOS.Services.Audio
{
class AudioInManager : IAudioInManager
{
private readonly AudioInManagerImpl _impl;
public AudioInManager(AudioInManagerImpl impl)
{
_impl = impl;
}
public string[] ListAudioIns(bool filtered)
{
return _impl.ListAudioIns(filtered);
}
public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle)
{
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
ResultCode result = (ResultCode)_impl.OpenAudioIn(out outputDeviceName, out outputConfiguration, out AudioInputSystem inSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle);
if (result == ResultCode.Success)
{
obj = new AudioIn.AudioIn(inSystem, context.Device.System.KernelContext, processHandle);
}
else
{
obj = null;
}
return result;
}
}
}

View file

@ -1,243 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audin:u")]
class AudioInManagerServer : IpcService
{
private const int AudioInNameSize = 0x100;
private readonly IAudioInManager _impl;
public AudioInManagerServer(ServiceCtx context) : this(context, new AudioInManager(context.Device.System.AudioInputManager)) { }
public AudioInManagerServer(ServiceCtx context, IAudioInManager impl) : base(context.Device.System.AudOutServer)
{
_impl = impl;
}
[CommandCmif(0)]
// ListAudioIns() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioIns(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioIns(false);
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
position += AudioInNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(1)]
// OpenAudioIn(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
public ResultCode OpenAudioIn(ServiceCtx context)
{
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
#pragma warning restore IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioInServer(obj));
}
return resultCode;
}
[CommandCmif(2)] // 3.0.0+
// ListAudioInsAuto() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioInsAuto(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioIns(false);
(ulong position, ulong size) = context.Request.GetBufferType0x22();
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
position += AudioInNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(3)] // 3.0.0+
// OpenAudioInAuto(AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 0x21>)
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 0x22> name)
public ResultCode OpenAudioInAuto(ServiceCtx context)
{
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
(ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
#pragma warning disable IDE0059 // Remove unnecessary value assignment
(ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
#pragma warning restore IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioInServer(obj));
}
return resultCode;
}
[CommandCmif(4)] // 3.0.0+
// ListAudioInsAutoFiltered() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioInsAutoFiltered(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioIns(true);
(ulong position, ulong size) = context.Request.GetBufferType0x22();
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioInNameSize - buffer.Length);
position += AudioInNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(5)] // 5.0.0+
// OpenAudioInProtocolSpecified(b64 protocol_specified_related, AudioInInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name)
// -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioIn>, buffer<bytes, 6> name)
public ResultCode OpenAudioInProtocolSpecified(ServiceCtx context)
{
// NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices).
#pragma warning disable IDE0059 // Remove unnecessary value assignment
bool protocolSpecifiedRelated = context.RequestData.ReadUInt64() == 1;
#pragma warning restore IDE0059
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
#pragma warning disable IDE0051, IDE0059 // Remove unused private member
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
#pragma warning restore IDE0051, IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioIn(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioInServer(obj));
}
return resultCode;
}
}
}

View file

@ -1,108 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Output;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
{
class AudioOut : IAudioOut
{
private readonly AudioOutputSystem _system;
private readonly uint _processHandle;
private readonly KernelContext _kernelContext;
public AudioOut(AudioOutputSystem system, KernelContext kernelContext, uint processHandle)
{
_system = system;
_kernelContext = kernelContext;
_processHandle = processHandle;
}
public ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer)
{
return (ResultCode)_system.AppendBuffer(bufferTag, ref buffer);
}
public bool ContainsBuffer(ulong bufferTag)
{
return _system.ContainsBuffer(bufferTag);
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_system.Dispose();
_kernelContext.Syscall.CloseHandle((int)_processHandle);
}
}
public bool FlushBuffers()
{
return _system.FlushBuffers();
}
public uint GetBufferCount()
{
return _system.GetBufferCount();
}
public ulong GetPlayedSampleCount()
{
return _system.GetPlayedSampleCount();
}
public ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount)
{
return (ResultCode)_system.GetReleasedBuffer(releasedBuffers, out releasedCount);
}
public AudioDeviceState GetState()
{
return _system.GetState();
}
public float GetVolume()
{
return _system.GetVolume();
}
public KEvent RegisterBufferEvent()
{
IWritableEvent outEvent = _system.RegisterBufferEvent();
if (outEvent is AudioKernelEvent kernelEvent)
{
return kernelEvent.Event;
}
else
{
throw new NotImplementedException();
}
}
public void SetVolume(float volume)
{
_system.SetVolume(volume);
}
public ResultCode Start()
{
return (ResultCode)_system.Start();
}
public ResultCode Stop()
{
return (ResultCode)_system.Stop();
}
}
}

View file

@ -1,181 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using Ryujinx.Memory;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
{
class AudioOutServer : DisposableIpcService
{
private readonly IAudioOut _impl;
public AudioOutServer(IAudioOut impl)
{
_impl = impl;
}
[CommandCmif(0)]
// GetAudioOutState() -> u32 state
public ResultCode GetAudioOutState(ServiceCtx context)
{
context.ResponseData.Write((uint)_impl.GetState());
return ResultCode.Success;
}
[CommandCmif(1)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return _impl.Start();
}
[CommandCmif(2)]
// Stop()
public ResultCode Stop(ServiceCtx context)
{
return _impl.Stop();
}
[CommandCmif(3)]
// AppendAudioOutBuffer(u64 bufferTag, buffer<nn::audio::AudioOutBuffer, 5> buffer)
public ResultCode AppendAudioOutBuffer(ServiceCtx context)
{
ulong position = context.Request.SendBuff[0].Position;
ulong bufferTag = context.RequestData.ReadUInt64();
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendBuffer(bufferTag, ref data);
}
[CommandCmif(4)]
// RegisterBufferEvent() -> handle<copy>
public ResultCode RegisterBufferEvent(ServiceCtx context)
{
KEvent bufferEvent = _impl.RegisterBufferEvent();
if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
return ResultCode.Success;
}
[CommandCmif(5)]
// GetReleasedAudioOutBuffers() -> (u32 count, buffer<u64, 6> tags)
public ResultCode GetReleasedAudioOutBuffers(ServiceCtx context)
{
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
context.ResponseData.Write(releasedCount);
return result;
}
[CommandCmif(6)]
// ContainsAudioOutBuffer(u64 tag) -> b8
public ResultCode ContainsAudioOutBuffer(ServiceCtx context)
{
ulong bufferTag = context.RequestData.ReadUInt64();
context.ResponseData.Write(_impl.ContainsBuffer(bufferTag));
return ResultCode.Success;
}
[CommandCmif(7)] // 3.0.0+
// AppendAudioOutBufferAuto(u64 tag, buffer<nn::audio::AudioOutBuffer, 0x21>)
public ResultCode AppendAudioOutBufferAuto(ServiceCtx context)
{
(ulong position, _) = context.Request.GetBufferType0x21();
ulong bufferTag = context.RequestData.ReadUInt64();
AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position);
return _impl.AppendBuffer(bufferTag, ref data);
}
[CommandCmif(8)] // 3.0.0+
// GetReleasedAudioOutBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags)
public ResultCode GetReleasedAudioOutBuffersAuto(ServiceCtx context)
{
(ulong position, ulong size) = context.Request.GetBufferType0x22();
using WritableRegion outputRegion = context.Memory.GetWritableRegion(position, (int)size);
ResultCode result = _impl.GetReleasedBuffers(MemoryMarshal.Cast<byte, ulong>(outputRegion.Memory.Span), out uint releasedCount);
context.ResponseData.Write(releasedCount);
return result;
}
[CommandCmif(9)] // 4.0.0+
// GetAudioOutBufferCount() -> u32
public ResultCode GetAudioOutBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetBufferCount());
return ResultCode.Success;
}
[CommandCmif(10)] // 4.0.0+
// GetAudioOutPlayedSampleCount() -> u64
public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetPlayedSampleCount());
return ResultCode.Success;
}
[CommandCmif(11)] // 4.0.0+
// FlushAudioOutBuffers() -> b8
public ResultCode FlushAudioOutBuffers(ServiceCtx context)
{
context.ResponseData.Write(_impl.FlushBuffers());
return ResultCode.Success;
}
[CommandCmif(12)] // 6.0.0+
// SetAudioOutVolume(s32)
public ResultCode SetAudioOutVolume(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
_impl.SetVolume(volume);
return ResultCode.Success;
}
[CommandCmif(13)] // 6.0.0+
// GetAudioOutVolume() -> s32
public ResultCode GetAudioOutVolume(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetVolume());
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_impl.Dispose();
}
}
}
}

View file

@ -1,33 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut
{
interface IAudioOut : IDisposable
{
AudioDeviceState GetState();
ResultCode Start();
ResultCode Stop();
ResultCode AppendBuffer(ulong bufferTag, ref AudioUserBuffer buffer);
KEvent RegisterBufferEvent();
ResultCode GetReleasedBuffers(Span<ulong> releasedBuffers, out uint releasedCount);
bool ContainsBuffer(ulong bufferTag);
uint GetBufferCount();
ulong GetPlayedSampleCount();
bool FlushBuffers();
void SetVolume(float volume);
float GetVolume();
}
}

View file

@ -1,40 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Output;
using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
using AudioOutManagerImpl = Ryujinx.Audio.Output.AudioOutputManager;
namespace Ryujinx.HLE.HOS.Services.Audio
{
class AudioOutManager : IAudioOutManager
{
private readonly AudioOutManagerImpl _impl;
public AudioOutManager(AudioOutManagerImpl impl)
{
_impl = impl;
}
public string[] ListAudioOuts()
{
return _impl.ListAudioOuts();
}
public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume)
{
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
ResultCode result = (ResultCode)_impl.OpenAudioOut(out outputDeviceName, out outputConfiguration, out AudioOutputSystem outSystem, memoryManager, inputDeviceName, SampleFormat.PcmInt16, ref parameter, appletResourceUserId, processHandle, volume);
if (result == ResultCode.Success)
{
obj = new AudioOut.AudioOut(outSystem, context.Device.System.KernelContext, processHandle);
}
else
{
obj = null;
}
return result;
}
}
}

View file

@ -1,166 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audout:u")]
class AudioOutManagerServer : IpcService
{
private const int AudioOutNameSize = 0x100;
private readonly IAudioOutManager _impl;
public AudioOutManagerServer(ServiceCtx context) : this(context, new AudioOutManager(context.Device.System.AudioOutputManager)) { }
public AudioOutManagerServer(ServiceCtx context, IAudioOutManager impl) : base(context.Device.System.AudOutServer)
{
_impl = impl;
}
[CommandCmif(0)]
// ListAudioOuts() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioOuts(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioOuts();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
position += AudioOutNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(1)]
// OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 5> name_in)
// -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 6> name_out)
public ResultCode OpenAudioOut(ServiceCtx context)
{
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ulong deviceNameInputPosition = context.Request.SendBuff[0].Position;
ulong deviceNameInputSize = context.Request.SendBuff[0].Size;
ulong deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position;
#pragma warning disable IDE0059 // Remove unnecessary value assignment
ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size;
#pragma warning restore IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioOutServer(obj));
}
return resultCode;
}
[CommandCmif(2)] // 3.0.0+
// ListAudioOutsAuto() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioOutsAuto(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioOuts();
(ulong position, ulong size) = context.Request.GetBufferType0x22();
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioOutNameSize - buffer.Length);
position += AudioOutNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(3)] // 3.0.0+
// OpenAudioOut(AudioOutInputConfiguration input_config, nn::applet::AppletResourceUserId, pid, handle<copy, process> process_handle, buffer<bytes, 0x21> name_in)
// -> (AudioOutInputConfiguration output_config, object<nn::audio::detail::IAudioOut>, buffer<bytes, 0x22> name_out)
public ResultCode OpenAudioOutAuto(ServiceCtx context)
{
AudioInputConfiguration inputConfiguration = context.RequestData.ReadStruct<AudioInputConfiguration>();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
(ulong deviceNameInputPosition, ulong deviceNameInputSize) = context.Request.GetBufferType0x21();
#pragma warning disable IDE0059 // Remove unnecessary value assignment
(ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22();
#pragma warning restore IDE0059
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0];
string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, (long)deviceNameInputSize);
ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle, context.Device.Configuration.AudioVolume);
if (resultCode == ResultCode.Success)
{
context.ResponseData.WriteStruct(outputConfiguration);
byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName);
context.Memory.Write(deviceNameOutputPosition, outputDeviceNameRaw);
MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + (ulong)outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length);
MakeObject(context, new AudioOutServer(obj));
}
return resultCode;
}
}
}

View file

@ -1,174 +0,0 @@
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.HLE.HOS.Kernel;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioDevice : IAudioDevice
{
private readonly VirtualDeviceSession[] _sessions;
#pragma warning disable IDE0052 // Remove unread private member
private readonly ulong _appletResourceId;
private readonly int _revision;
#pragma warning restore IDE0052
private readonly bool _isUsbDeviceSupported;
private readonly VirtualDeviceSessionRegistry _registry;
private readonly KEvent _systemEvent;
public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision)
{
_registry = registry;
_appletResourceId = appletResourceId;
_revision = revision;
BehaviourContext behaviourContext = new();
behaviourContext.SetUserRevision(revision);
_isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
_sessions = _registry.GetSessionByAppletResourceId(appletResourceId);
// TODO: support the 3 different events correctly when we will have hot plugable audio devices.
_systemEvent = new KEvent(context);
_systemEvent.ReadableEvent.Signal();
}
private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
{
result = null;
foreach (VirtualDeviceSession session in _sessions)
{
if (session.Device.Name.Equals(name))
{
if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
{
return false;
}
result = session;
return true;
}
}
return false;
}
public string GetActiveAudioDeviceName()
{
VirtualDevice device = _registry.ActiveDevice;
if (!_isUsbDeviceSupported && device.IsUsbDevice())
{
device = _registry.DefaultDevice;
}
return device.Name;
}
public uint GetActiveChannelCount()
{
VirtualDevice device = _registry.ActiveDevice;
if (!_isUsbDeviceSupported && device.IsUsbDevice())
{
device = _registry.DefaultDevice;
}
return device.ChannelCount;
}
public ResultCode GetAudioDeviceOutputVolume(string name, out float volume)
{
if (TryGetDeviceByName(out VirtualDeviceSession result, name))
{
volume = result.Volume;
}
else
{
volume = 0.0f;
}
return ResultCode.Success;
}
public ResultCode SetAudioDeviceOutputVolume(string name, float volume)
{
if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
{
if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
{
result = _sessions[0];
}
result.Volume = volume;
}
return ResultCode.Success;
}
public string GetActiveAudioOutputDeviceName()
{
return _registry.ActiveDevice.GetOutputDeviceName();
}
public string[] ListAudioDeviceName()
{
int deviceCount = _sessions.Length;
if (!_isUsbDeviceSupported)
{
deviceCount--;
}
string[] result = new string[deviceCount];
int i = 0;
foreach (VirtualDeviceSession session in _sessions)
{
if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
{
continue;
}
result[i] = session.Device.Name;
i++;
}
return result;
}
public string[] ListAudioOutputDeviceName()
{
int deviceCount = _sessions.Length;
string[] result = new string[deviceCount];
for (int i = 0; i < deviceCount; i++)
{
result[i] = _sessions[i].Device.GetOutputDeviceName();
}
return result;
}
public KEvent QueryAudioDeviceInputEvent()
{
return _systemEvent;
}
public KEvent QueryAudioDeviceOutputEvent()
{
return _systemEvent;
}
public KEvent QueryAudioDeviceSystemEvent()
{
return _systemEvent;
}
}
}

View file

@ -1,320 +0,0 @@
using Ryujinx.Common.Logging;
using Ryujinx.Cpu;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
using System.Text;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioDeviceServer : IpcService
{
private const int AudioDeviceNameSize = 0x100;
private readonly IAudioDevice _impl;
public AudioDeviceServer(IAudioDevice impl)
{
_impl = impl;
}
[CommandCmif(0)]
// ListAudioDeviceName() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioDeviceName(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioDeviceName();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
position += AudioDeviceNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(1)]
// SetAudioDeviceOutputVolume(f32 volume, buffer<bytes, 5> name)
public ResultCode SetAudioDeviceOutputVolume(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
ulong position = context.Request.SendBuff[0].Position;
ulong size = context.Request.SendBuff[0].Size;
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
}
[CommandCmif(2)]
// GetAudioDeviceOutputVolume(buffer<bytes, 5> name) -> f32 volume
public ResultCode GetAudioDeviceOutputVolume(ServiceCtx context)
{
ulong position = context.Request.SendBuff[0].Position;
ulong size = context.Request.SendBuff[0].Size;
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
if (result == ResultCode.Success)
{
context.ResponseData.Write(volume);
}
return result;
}
[CommandCmif(3)]
// GetActiveAudioDeviceName() -> buffer<bytes, 6>
public ResultCode GetActiveAudioDeviceName(ServiceCtx context)
{
string name = _impl.GetActiveAudioDeviceName();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
if ((ulong)deviceNameBuffer.Length <= size)
{
context.Memory.Write(position, deviceNameBuffer);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
}
return ResultCode.Success;
}
[CommandCmif(4)]
// QueryAudioDeviceSystemEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceSystemEvent(ServiceCtx context)
{
KEvent deviceSystemEvent = _impl.QueryAudioDeviceSystemEvent();
if (context.Process.HandleTable.GenerateHandle(deviceSystemEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[CommandCmif(5)]
// GetActiveChannelCount() -> u32
public ResultCode GetActiveChannelCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetActiveChannelCount());
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[CommandCmif(6)] // 3.0.0+
// ListAudioDeviceNameAuto() -> (u32, buffer<bytes, 0x22>)
public ResultCode ListAudioDeviceNameAuto(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioDeviceName();
(ulong position, ulong size) = context.Request.GetBufferType0x22();
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
position += AudioDeviceNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
[CommandCmif(7)] // 3.0.0+
// SetAudioDeviceOutputVolumeAuto(f32 volume, buffer<bytes, 0x21> name)
public ResultCode SetAudioDeviceOutputVolumeAuto(ServiceCtx context)
{
float volume = context.RequestData.ReadSingle();
(ulong position, ulong size) = context.Request.GetBufferType0x21();
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
return _impl.SetAudioDeviceOutputVolume(deviceName, volume);
}
[CommandCmif(8)] // 3.0.0+
// GetAudioDeviceOutputVolumeAuto(buffer<bytes, 0x21> name) -> f32
public ResultCode GetAudioDeviceOutputVolumeAuto(ServiceCtx context)
{
(ulong position, ulong size) = context.Request.GetBufferType0x21();
string deviceName = MemoryHelper.ReadAsciiString(context.Memory, position, (long)size);
ResultCode result = _impl.GetAudioDeviceOutputVolume(deviceName, out float volume);
if (result == ResultCode.Success)
{
context.ResponseData.Write(volume);
}
return ResultCode.Success;
}
[CommandCmif(10)] // 3.0.0+
// GetActiveAudioDeviceNameAuto() -> buffer<bytes, 0x22>
public ResultCode GetActiveAudioDeviceNameAuto(ServiceCtx context)
{
string name = _impl.GetActiveAudioDeviceName();
(ulong position, ulong size) = context.Request.GetBufferType0x22();
byte[] deviceNameBuffer = Encoding.UTF8.GetBytes(name + '\0');
if ((ulong)deviceNameBuffer.Length <= size)
{
context.Memory.Write(position, deviceNameBuffer);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
}
return ResultCode.Success;
}
[CommandCmif(11)] // 3.0.0+
// QueryAudioDeviceInputEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceInputEvent(ServiceCtx context)
{
KEvent deviceInputEvent = _impl.QueryAudioDeviceInputEvent();
if (context.Process.HandleTable.GenerateHandle(deviceInputEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[CommandCmif(12)] // 3.0.0+
// QueryAudioDeviceOutputEvent() -> handle<copy, event>
public ResultCode QueryAudioDeviceOutputEvent(ServiceCtx context)
{
KEvent deviceOutputEvent = _impl.QueryAudioDeviceOutputEvent();
if (context.Process.HandleTable.GenerateHandle(deviceOutputEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return ResultCode.Success;
}
[CommandCmif(13)] // 13.0.0+
// GetActiveAudioOutputDeviceName() -> buffer<bytes, 6>
public ResultCode GetActiveAudioOutputDeviceName(ServiceCtx context)
{
string name = _impl.GetActiveAudioOutputDeviceName();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(name + "\0");
if ((ulong)deviceNameBuffer.Length <= size)
{
context.Memory.Write(position, deviceNameBuffer);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!");
}
return ResultCode.Success;
}
[CommandCmif(14)] // 13.0.0+
// ListAudioOutputDeviceName() -> (u32, buffer<bytes, 6>)
public ResultCode ListAudioOutputDeviceName(ServiceCtx context)
{
string[] deviceNames = _impl.ListAudioOutputDeviceName();
ulong position = context.Request.ReceiveBuff[0].Position;
ulong size = context.Request.ReceiveBuff[0].Size;
ulong basePosition = position;
int count = 0;
foreach (string name in deviceNames)
{
byte[] buffer = Encoding.ASCII.GetBytes(name);
if ((position - basePosition) + (ulong)buffer.Length > size)
{
break;
}
context.Memory.Write(position, buffer);
MemoryHelper.FillWithZeros(context.Memory, position + (ulong)buffer.Length, AudioDeviceNameSize - buffer.Length);
position += AudioDeviceNameSize;
count++;
}
context.ResponseData.Write(count);
return ResultCode.Success;
}
}
}

View file

@ -1,25 +0,0 @@
using Ryujinx.Audio.Integration;
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioKernelEvent : IWritableEvent
{
public KEvent Event { get; }
public AudioKernelEvent(KEvent evnt)
{
Event = evnt;
}
public void Clear()
{
Event.WritableEvent.Clear();
}
public void Signal()
{
Event.WritableEvent.Signal();
}
}
}

View file

@ -1,122 +0,0 @@
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioRenderer : IAudioRenderer
{
private readonly AudioRenderSystem _impl;
public AudioRenderer(AudioRenderSystem impl)
{
_impl = impl;
}
public ResultCode ExecuteAudioRendererRendering()
{
return (ResultCode)_impl.ExecuteAudioRendererRendering();
}
public uint GetMixBufferCount()
{
return _impl.GetMixBufferCount();
}
public uint GetRenderingTimeLimit()
{
return _impl.GetRenderingTimeLimit();
}
public uint GetSampleCount()
{
return _impl.GetSampleCount();
}
public uint GetSampleRate()
{
return _impl.GetSampleRate();
}
public int GetState()
{
if (_impl.IsActive())
{
return 0;
}
return 1;
}
public ResultCode QuerySystemEvent(out KEvent systemEvent)
{
ResultCode resultCode = (ResultCode)_impl.QuerySystemEvent(out IWritableEvent outEvent);
if (resultCode == ResultCode.Success)
{
if (outEvent is AudioKernelEvent kernelEvent)
{
systemEvent = kernelEvent.Event;
}
else
{
throw new NotImplementedException();
}
}
else
{
systemEvent = null;
}
return resultCode;
}
public ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input)
{
return (ResultCode)_impl.Update(output, performanceOutput, input);
}
public void SetRenderingTimeLimit(uint percent)
{
_impl.SetRenderingTimeLimitPercent(percent);
}
public ResultCode Start()
{
_impl.Start();
return ResultCode.Success;
}
public ResultCode Stop()
{
_impl.Stop();
return ResultCode.Success;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_impl.Dispose();
}
}
public void SetVoiceDropParameter(float voiceDropParameter)
{
_impl.SetVoiceDropParameter(voiceDropParameter);
}
public float GetVoiceDropParameter()
{
return _impl.GetVoiceDropParameter();
}
}
}

View file

@ -1,215 +0,0 @@
using Ryujinx.Common.Logging;
using Ryujinx.Common.Memory;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Threading;
using Ryujinx.Horizon.Common;
using System;
using System.Buffers;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioRendererServer : DisposableIpcService
{
private readonly IAudioRenderer _impl;
public AudioRendererServer(IAudioRenderer impl)
{
_impl = impl;
}
[CommandCmif(0)]
// GetSampleRate() -> u32
public ResultCode GetSampleRate(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetSampleRate());
return ResultCode.Success;
}
[CommandCmif(1)]
// GetSampleCount() -> u32
public ResultCode GetSampleCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetSampleCount());
return ResultCode.Success;
}
[CommandCmif(2)]
// GetMixBufferCount() -> u32
public ResultCode GetMixBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetMixBufferCount());
return ResultCode.Success;
}
[CommandCmif(3)]
// GetState() -> u32
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetState());
return ResultCode.Success;
}
[CommandCmif(4)]
// RequestUpdate(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 5> input)
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 6> performanceOutput)
public ResultCode RequestUpdate(ServiceCtx context)
{
ulong inputPosition = context.Request.SendBuff[0].Position;
ulong inputSize = context.Request.SendBuff[0].Size;
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
ulong performanceOutputPosition = context.Request.ReceiveBuff[1].Position;
ulong performanceOutputSize = context.Request.ReceiveBuff[1].Size;
ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
using IMemoryOwner<byte> outputOwner = ByteMemoryPool.RentCleared(outputSize);
using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.RentCleared(performanceOutputSize);
Memory<byte> output = outputOwner.Memory;
Memory<byte> performanceOutput = performanceOutputOwner.Memory;
using MemoryHandle outputHandle = output.Pin();
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
if (result == ResultCode.Success)
{
context.Memory.Write(outputPosition, output.Span);
context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}");
}
return result;
}
[CommandCmif(5)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return _impl.Start();
}
[CommandCmif(6)]
// Stop()
public ResultCode Stop(ServiceCtx context)
{
return _impl.Stop();
}
[CommandCmif(7)]
// QuerySystemEvent() -> handle<copy, event>
public ResultCode QuerySystemEvent(ServiceCtx context)
{
ResultCode result = _impl.QuerySystemEvent(out KEvent systemEvent);
if (result == ResultCode.Success)
{
if (context.Process.HandleTable.GenerateHandle(systemEvent.ReadableEvent, out int handle) != Result.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
}
return result;
}
[CommandCmif(8)]
// SetAudioRendererRenderingTimeLimit(u32 limit)
public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context)
{
uint limit = context.RequestData.ReadUInt32();
_impl.SetRenderingTimeLimit(limit);
return ResultCode.Success;
}
[CommandCmif(9)]
// GetAudioRendererRenderingTimeLimit() -> u32 limit
public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context)
{
uint limit = _impl.GetRenderingTimeLimit();
context.ResponseData.Write(limit);
return ResultCode.Success;
}
[CommandCmif(10)] // 3.0.0+
// RequestUpdateAuto(buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x21> input)
// -> (buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> output, buffer<nn::audio::detail::AudioRendererUpdateDataHeader, 0x22> performanceOutput)
public ResultCode RequestUpdateAuto(ServiceCtx context)
{
(ulong inputPosition, ulong inputSize) = context.Request.GetBufferType0x21();
(ulong outputPosition, ulong outputSize) = context.Request.GetBufferType0x22(0);
(ulong performanceOutputPosition, ulong performanceOutputSize) = context.Request.GetBufferType0x22(1);
ReadOnlyMemory<byte> input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray();
Memory<byte> output = new byte[outputSize];
Memory<byte> performanceOutput = new byte[performanceOutputSize];
using MemoryHandle outputHandle = output.Pin();
using MemoryHandle performanceOutputHandle = performanceOutput.Pin();
ResultCode result = _impl.RequestUpdate(output, performanceOutput, input);
if (result == ResultCode.Success)
{
context.Memory.Write(outputPosition, output.Span);
context.Memory.Write(performanceOutputPosition, performanceOutput.Span);
}
return result;
}
[CommandCmif(11)] // 3.0.0+
// ExecuteAudioRendererRendering()
public ResultCode ExecuteAudioRendererRendering(ServiceCtx context)
{
return _impl.ExecuteAudioRendererRendering();
}
[CommandCmif(12)] // 15.0.0+
// SetVoiceDropParameter(f32 voiceDropParameter)
public ResultCode SetVoiceDropParameter(ServiceCtx context)
{
float voiceDropParameter = context.RequestData.ReadSingle();
_impl.SetVoiceDropParameter(voiceDropParameter);
return ResultCode.Success;
}
[CommandCmif(13)] // 15.0.0+
// GetVoiceDropParameter() -> f32 voiceDropParameter
public ResultCode GetVoiceDropParameter(ServiceCtx context)
{
float voiceDropParameter = _impl.GetVoiceDropParameter();
context.ResponseData.Write(voiceDropParameter);
return ResultCode.Success;
}
protected override void Dispose(bool isDisposing)
{
if (isDisposing)
{
_impl.Dispose();
}
}
}
}

View file

@ -1,18 +0,0 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
interface IAudioDevice
{
string[] ListAudioDeviceName();
ResultCode SetAudioDeviceOutputVolume(string name, float volume);
ResultCode GetAudioDeviceOutputVolume(string name, out float volume);
string GetActiveAudioDeviceName();
KEvent QueryAudioDeviceSystemEvent();
uint GetActiveChannelCount();
KEvent QueryAudioDeviceInputEvent();
KEvent QueryAudioDeviceOutputEvent();
string GetActiveAudioOutputDeviceName();
string[] ListAudioOutputDeviceName();
}
}

View file

@ -1,22 +0,0 @@
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
interface IAudioRenderer : IDisposable
{
uint GetSampleRate();
uint GetSampleCount();
uint GetMixBufferCount();
int GetState();
ResultCode RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input);
ResultCode Start();
ResultCode Stop();
ResultCode QuerySystemEvent(out KEvent systemEvent);
void SetRenderingTimeLimit(uint percent);
uint GetRenderingTimeLimit();
ResultCode ExecuteAudioRendererRendering();
void SetVoiceDropParameter(float voiceDropParameter);
float GetVoiceDropParameter();
}
}

View file

@ -1,67 +0,0 @@
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
using AudioRendererManagerImpl = Ryujinx.Audio.Renderer.Server.AudioRendererManager;
namespace Ryujinx.HLE.HOS.Services.Audio
{
class AudioRendererManager : IAudioRendererManager
{
private readonly AudioRendererManagerImpl _impl;
private readonly VirtualDeviceSessionRegistry _registry;
public AudioRendererManager(AudioRendererManagerImpl impl, VirtualDeviceSessionRegistry registry)
{
_impl = impl;
_registry = registry;
}
public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId)
{
outObject = new AudioDevice(_registry, context.Device.System.KernelContext, appletResourceUserId, revision);
return ResultCode.Success;
}
public ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter)
{
return AudioRendererManagerImpl.GetWorkBufferSize(ref parameter);
}
public ResultCode OpenAudioRenderer(
ServiceCtx context,
out IAudioRenderer obj,
ref AudioRendererConfiguration parameter,
ulong workBufferSize,
ulong appletResourceUserId,
KTransferMemory workBufferTransferMemory,
uint processHandle)
{
var memoryManager = context.Process.HandleTable.GetKProcess((int)processHandle).CpuMemory;
ResultCode result = (ResultCode)_impl.OpenAudioRenderer(
out AudioRenderSystem renderer,
memoryManager,
ref parameter,
appletResourceUserId,
workBufferTransferMemory.Address,
workBufferTransferMemory.Size,
processHandle,
context.Device.Configuration.AudioVolume);
if (result == ResultCode.Success)
{
obj = new AudioRenderer.AudioRenderer(renderer);
}
else
{
obj = null;
}
return result;
}
}
}

View file

@ -1,116 +0,0 @@
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Common;
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audren:u")]
class AudioRendererManagerServer : IpcService
{
private const int InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24);
private readonly IAudioRendererManager _impl;
public AudioRendererManagerServer(ServiceCtx context) : this(context, new AudioRendererManager(context.Device.System.AudioRendererManager, context.Device.System.AudioDeviceSessionRegistry)) { }
public AudioRendererManagerServer(ServiceCtx context, IAudioRendererManager impl) : base(context.Device.System.AudRenServer)
{
_impl = impl;
}
[CommandCmif(0)]
// OpenAudioRenderer(nn::audio::detail::AudioRendererParameterInternal parameter, u64 workBufferSize, nn::applet::AppletResourceUserId appletResourceId, pid, handle<copy> workBuffer, handle<copy> processHandle)
// -> object<nn::audio::detail::IAudioRenderer>
public ResultCode OpenAudioRenderer(ServiceCtx context)
{
AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
ulong workBufferSize = context.RequestData.ReadUInt64();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
int transferMemoryHandle = context.Request.HandleDesc.ToCopy[0];
KTransferMemory workBufferTransferMemory = context.Process.HandleTable.GetObject<KTransferMemory>(transferMemoryHandle);
uint processHandle = (uint)context.Request.HandleDesc.ToCopy[1];
ResultCode result = _impl.OpenAudioRenderer(
context,
out IAudioRenderer renderer,
ref parameter,
workBufferSize,
appletResourceUserId,
workBufferTransferMemory,
processHandle);
if (result == ResultCode.Success)
{
MakeObject(context, new AudioRendererServer(renderer));
}
context.Device.System.KernelContext.Syscall.CloseHandle(transferMemoryHandle);
context.Device.System.KernelContext.Syscall.CloseHandle((int)processHandle);
return result;
}
[CommandCmif(1)]
// GetWorkBufferSize(nn::audio::detail::AudioRendererParameterInternal parameter) -> u64 workBufferSize
public ResultCode GetAudioRendererWorkBufferSize(ServiceCtx context)
{
AudioRendererConfiguration parameter = context.RequestData.ReadStruct<AudioRendererConfiguration>();
if (BehaviourContext.CheckValidRevision(parameter.Revision))
{
ulong size = _impl.GetWorkBufferSize(ref parameter);
context.ResponseData.Write(size);
Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{size:x16}.");
return ResultCode.Success;
}
else
{
context.ResponseData.Write(0L);
Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Revision)} is not supported!");
return ResultCode.UnsupportedRevision;
}
}
[CommandCmif(2)]
// GetAudioDeviceService(nn::applet::AppletResourceUserId) -> object<nn::audio::detail::IAudioDevice>
public ResultCode GetAudioDeviceService(ServiceCtx context)
{
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, InitialRevision, appletResourceUserId);
if (result == ResultCode.Success)
{
MakeObject(context, new AudioDeviceServer(device));
}
return result;
}
[CommandCmif(4)] // 4.0.0+
// GetAudioDeviceServiceWithRevisionInfo(s32 revision, nn::applet::AppletResourceUserId appletResourceId) -> object<nn::audio::detail::IAudioDevice>
public ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context)
{
int revision = context.RequestData.ReadInt32();
ulong appletResourceUserId = context.RequestData.ReadUInt64();
ResultCode result = _impl.GetAudioDeviceServiceWithRevisionInfo(context, out IAudioDevice device, revision, appletResourceUserId);
if (result == ResultCode.Success)
{
MakeObject(context, new AudioDeviceServer(device));
}
return result;
}
}
}

View file

@ -1,27 +0,0 @@
using Concentus.Structs;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
class Decoder : IDecoder
{
private readonly OpusDecoder _decoder;
public int SampleRate => _decoder.SampleRate;
public int ChannelsCount => _decoder.NumChannels;
public Decoder(int sampleRate, int channelsCount)
{
_decoder = new OpusDecoder(sampleRate, channelsCount);
}
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
{
return _decoder.Decode(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize);
}
public void ResetState()
{
_decoder.ResetState();
}
}
}

View file

@ -1,92 +0,0 @@
using Concentus;
using Concentus.Enums;
using Concentus.Structs;
using Ryujinx.HLE.HOS.Services.Audio.Types;
using System;
using System.Runtime.CompilerServices;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
static class DecoderCommon
{
private static ResultCode GetPacketNumSamples(this IDecoder decoder, out int numSamples, byte[] packet)
{
int result = OpusPacketInfo.GetNumSamples(packet, 0, packet.Length, decoder.SampleRate);
numSamples = result;
if (result == OpusError.OPUS_INVALID_PACKET)
{
return ResultCode.OpusInvalidInput;
}
else if (result == OpusError.OPUS_BAD_ARG)
{
return ResultCode.OpusInvalidInput;
}
return ResultCode.Success;
}
public static ResultCode DecodeInterleaved(
this IDecoder decoder,
bool reset,
ReadOnlySpan<byte> input,
out short[] outPcmData,
ulong outputSize,
out uint outConsumed,
out int outSamples)
{
outPcmData = null;
outConsumed = 0;
outSamples = 0;
int streamSize = input.Length;
if (streamSize < Unsafe.SizeOf<OpusPacketHeader>())
{
return ResultCode.OpusInvalidInput;
}
OpusPacketHeader header = OpusPacketHeader.FromSpan(input);
int headerSize = Unsafe.SizeOf<OpusPacketHeader>();
uint totalSize = header.length + (uint)headerSize;
if (totalSize > streamSize)
{
return ResultCode.OpusInvalidInput;
}
byte[] opusData = input.Slice(headerSize, (int)header.length).ToArray();
ResultCode result = decoder.GetPacketNumSamples(out int numSamples, opusData);
if (result == ResultCode.Success)
{
if ((uint)numSamples * (uint)decoder.ChannelsCount * sizeof(short) > outputSize)
{
return ResultCode.OpusInvalidInput;
}
outPcmData = new short[numSamples * decoder.ChannelsCount];
if (reset)
{
decoder.ResetState();
}
try
{
outSamples = decoder.Decode(opusData, 0, opusData.Length, outPcmData, 0, outPcmData.Length / decoder.ChannelsCount);
outConsumed = totalSize;
}
catch (OpusException)
{
// TODO: as OpusException doesn't provide us the exact error code, this is kind of inaccurate in some cases...
return ResultCode.OpusInvalidInput;
}
}
return ResultCode.Success;
}
}
}

View file

@ -1,11 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
interface IDecoder
{
int SampleRate { get; }
int ChannelsCount { get; }
int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize);
void ResetState();
}
}

View file

@ -1,116 +0,0 @@
using Ryujinx.HLE.HOS.Services.Audio.Types;
using System;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
class IHardwareOpusDecoder : IpcService
{
private readonly IDecoder _decoder;
private readonly OpusDecoderFlags _flags;
public IHardwareOpusDecoder(int sampleRate, int channelsCount, OpusDecoderFlags flags)
{
_decoder = new Decoder(sampleRate, channelsCount);
_flags = flags;
}
public IHardwareOpusDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, OpusDecoderFlags flags, byte[] mapping)
{
_decoder = new MultiSampleDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
_flags = flags;
}
[CommandCmif(0)]
// DecodeInterleavedOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
public ResultCode DecodeInterleavedOld(ServiceCtx context)
{
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
}
[CommandCmif(2)]
// DecodeInterleavedForMultiStreamOld(buffer<unknown, 5>) -> (u32, u32, buffer<unknown, 6>)
public ResultCode DecodeInterleavedForMultiStreamOld(ServiceCtx context)
{
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: false);
}
[CommandCmif(4)] // 6.0.0+
// DecodeInterleavedWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedWithPerfOld(ServiceCtx context)
{
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
}
[CommandCmif(5)] // 6.0.0+
// DecodeInterleavedForMultiStreamWithPerfOld(buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedForMultiStreamWithPerfOld(ServiceCtx context)
{
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset: false, withPerf: true);
}
[CommandCmif(6)] // 6.0.0+
// DecodeInterleavedWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedWithPerfAndResetOld(ServiceCtx context)
{
bool reset = context.RequestData.ReadBoolean();
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
}
[CommandCmif(7)] // 6.0.0+
// DecodeInterleavedForMultiStreamWithPerfAndResetOld(bool reset, buffer<unknown, 5>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedForMultiStreamWithPerfAndResetOld(ServiceCtx context)
{
bool reset = context.RequestData.ReadBoolean();
return DecodeInterleavedInternal(context, OpusDecoderFlags.None, reset, withPerf: true);
}
[CommandCmif(8)] // 7.0.0+
// DecodeInterleaved(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleaved(ServiceCtx context)
{
bool reset = context.RequestData.ReadBoolean();
return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
}
[CommandCmif(9)] // 7.0.0+
// DecodeInterleavedForMultiStream(bool reset, buffer<unknown, 0x45>) -> (u32, u32, u64, buffer<unknown, 0x46>)
public ResultCode DecodeInterleavedForMultiStream(ServiceCtx context)
{
bool reset = context.RequestData.ReadBoolean();
return DecodeInterleavedInternal(context, _flags, reset, withPerf: true);
}
private ResultCode DecodeInterleavedInternal(ServiceCtx context, OpusDecoderFlags flags, bool reset, bool withPerf)
{
ulong inPosition = context.Request.SendBuff[0].Position;
ulong inSize = context.Request.SendBuff[0].Size;
ulong outputPosition = context.Request.ReceiveBuff[0].Position;
ulong outputSize = context.Request.ReceiveBuff[0].Size;
ReadOnlySpan<byte> input = context.Memory.GetSpan(inPosition, (int)inSize);
ResultCode result = _decoder.DecodeInterleaved(reset, input, out short[] outPcmData, outputSize, out uint outConsumed, out int outSamples);
if (result == ResultCode.Success)
{
context.Memory.Write(outputPosition, MemoryMarshal.Cast<short, byte>(outPcmData.AsSpan()));
context.ResponseData.Write(outConsumed);
context.ResponseData.Write(outSamples);
if (withPerf)
{
// This is the time the DSP took to process the request, TODO: fill this.
context.ResponseData.Write(0UL);
}
}
return result;
}
}
}

View file

@ -1,28 +0,0 @@
using Concentus.Structs;
namespace Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager
{
class MultiSampleDecoder : IDecoder
{
private readonly OpusMSDecoder _decoder;
public int SampleRate => _decoder.SampleRate;
public int ChannelsCount { get; }
public MultiSampleDecoder(int sampleRate, int channelsCount, int streams, int coupledStreams, byte[] mapping)
{
ChannelsCount = channelsCount;
_decoder = new OpusMSDecoder(sampleRate, channelsCount, streams, coupledStreams, mapping);
}
public int Decode(byte[] inData, int inDataOffset, int len, short[] outPcm, int outPcmOffset, int frameSize)
{
return _decoder.DecodeMultistream(inData, inDataOffset, len, outPcm, outPcmOffset, frameSize, 0);
}
public void ResetState()
{
_decoder.ResetState();
}
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audctl")]
class IAudioController : IpcService
{
public IAudioController(ServiceCtx context) { }
}
}

View file

@ -1,12 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.HLE.HOS.Services.Audio.AudioIn;
namespace Ryujinx.HLE.HOS.Services.Audio
{
interface IAudioInManager
{
public string[] ListAudioIns(bool filtered);
public ResultCode OpenAudioIn(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioIn obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle);
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audin:a")]
class IAudioInManagerForApplet : IpcService
{
public IAudioInManagerForApplet(ServiceCtx context) { }
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audin:d")]
class IAudioInManagerForDebugger : IpcService
{
public IAudioInManagerForDebugger(ServiceCtx context) { }
}
}

View file

@ -1,12 +0,0 @@
using Ryujinx.Audio.Common;
using Ryujinx.HLE.HOS.Services.Audio.AudioOut;
namespace Ryujinx.HLE.HOS.Services.Audio
{
interface IAudioOutManager
{
public string[] ListAudioOuts();
public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle, float volume);
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audout:a")]
class IAudioOutManagerForApplet : IpcService
{
public IAudioOutManagerForApplet(ServiceCtx context) { }
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audout:d")]
class IAudioOutManagerForDebugger : IpcService
{
public IAudioOutManagerForDebugger(ServiceCtx context) { }
}
}

View file

@ -1,19 +0,0 @@
using Ryujinx.Audio.Renderer.Parameter;
using Ryujinx.HLE.HOS.Kernel.Memory;
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
namespace Ryujinx.HLE.HOS.Services.Audio
{
interface IAudioRendererManager
{
// TODO: Remove ServiceCtx argument
// BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
ResultCode GetAudioDeviceServiceWithRevisionInfo(ServiceCtx context, out IAudioDevice outObject, int revision, ulong appletResourceUserId);
// TODO: Remove ServiceCtx argument
// BODY: This is only needed by the legacy backend. Refactor this when removing the legacy backend.
ResultCode OpenAudioRenderer(ServiceCtx context, out IAudioRenderer obj, ref AudioRendererConfiguration parameter, ulong workBufferSize, ulong appletResourceUserId, KTransferMemory workBufferTransferMemory, uint processHandle);
ulong GetWorkBufferSize(ref AudioRendererConfiguration parameter);
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audren:a")]
class IAudioRendererManagerForApplet : IpcService
{
public IAudioRendererManagerForApplet(ServiceCtx context) { }
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audren:d")]
class IAudioRendererManagerForDebugger : IpcService
{
public IAudioRendererManagerForDebugger(ServiceCtx context) { }
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("auddev")] // 6.0.0+
class IAudioSnoopManager : IpcService
{
public IAudioSnoopManager(ServiceCtx context) { }
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audrec:u")]
class IFinalOutputRecorderManager : IpcService
{
public IFinalOutputRecorderManager(ServiceCtx context) { }
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audrec:a")]
class IFinalOutputRecorderManagerForApplet : IpcService
{
public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { }
}
}

View file

@ -1,8 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("audrec:d")]
class IFinalOutputRecorderManagerForDebugger : IpcService
{
public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { }
}
}

View file

@ -1,227 +0,0 @@
using Ryujinx.Common;
using Ryujinx.HLE.HOS.Services.Audio.HardwareOpusDecoderManager;
using Ryujinx.HLE.HOS.Services.Audio.Types;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio
{
[Service("hwopus")]
class IHardwareOpusDecoderManager : IpcService
{
public IHardwareOpusDecoderManager(ServiceCtx context) { }
[CommandCmif(0)]
// Initialize(bytes<8, 4>, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder>
public ResultCode Initialize(ServiceCtx context)
{
int sampleRate = context.RequestData.ReadInt32();
int channelsCount = context.RequestData.ReadInt32();
MakeObject(context, new IHardwareOpusDecoder(sampleRate, channelsCount, OpusDecoderFlags.None));
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return ResultCode.Success;
}
[CommandCmif(1)]
// GetWorkBufferSize(bytes<8, 4>) -> u32
public ResultCode GetWorkBufferSize(ServiceCtx context)
{
int sampleRate = context.RequestData.ReadInt32();
int channelsCount = context.RequestData.ReadInt32();
int opusDecoderSize = GetOpusDecoderSize(channelsCount);
int frameSize = BitUtils.AlignUp(channelsCount * 1920 / (48000 / sampleRate), 64);
int totalSize = opusDecoderSize + 1536 + frameSize;
context.ResponseData.Write(totalSize);
return ResultCode.Success;
}
[CommandCmif(2)] // 3.0.0+
// InitializeForMultiStream(u32, handle<copy>, buffer<unknown<0x110>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
public ResultCode InitializeForMultiStream(ServiceCtx context)
{
ulong parametersAddress = context.Request.PtrBuff[0].Position;
OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, OpusDecoderFlags.None));
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return ResultCode.Success;
}
[CommandCmif(3)] // 3.0.0+
// GetWorkBufferSizeForMultiStream(buffer<unknown<0x110>, 0x19>) -> u32
public ResultCode GetWorkBufferSizeForMultiStream(ServiceCtx context)
{
ulong parametersAddress = context.Request.PtrBuff[0].Position;
OpusMultiStreamParameters parameters = context.Memory.Read<OpusMultiStreamParameters>(parametersAddress);
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * 1920 / (48000 / parameters.SampleRate), 64);
int totalSize = opusDecoderSize + streamSize + frameSize;
context.ResponseData.Write(totalSize);
return ResultCode.Success;
}
[CommandCmif(4)] // 12.0.0+
// InitializeEx(OpusParametersEx, u32, handle<copy>) -> object<nn::codec::detail::IHardwareOpusDecoder>
public ResultCode InitializeEx(ServiceCtx context)
{
OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
// UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
MakeObject(context, new IHardwareOpusDecoder(parameters.SampleRate, parameters.ChannelsCount, parameters.Flags));
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return ResultCode.Success;
}
[CommandCmif(5)] // 12.0.0+
// GetWorkBufferSizeEx(OpusParametersEx) -> u32
public ResultCode GetWorkBufferSizeEx(ServiceCtx context)
{
OpusParametersEx parameters = context.RequestData.ReadStruct<OpusParametersEx>();
int opusDecoderSize = GetOpusDecoderSize(parameters.ChannelsCount);
int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
int totalSize = opusDecoderSize + 1536 + frameSize;
context.ResponseData.Write(totalSize);
return ResultCode.Success;
}
[CommandCmif(6)] // 12.0.0+
// InitializeForMultiStreamEx(u32, handle<copy>, buffer<unknown<0x118>, 0x19>) -> object<nn::codec::detail::IHardwareOpusDecoder>
public ResultCode InitializeForMultiStreamEx(ServiceCtx context)
{
ulong parametersAddress = context.Request.PtrBuff[0].Position;
OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
byte[] mappings = MemoryMarshal.Cast<uint, byte>(parameters.ChannelMappings.AsSpan()).ToArray();
// UseLargeFrameSize can be ignored due to not relying on fixed size buffers for storing the decoded result.
MakeObject(context, new IHardwareOpusDecoder(
parameters.SampleRate,
parameters.ChannelsCount,
parameters.NumberOfStreams,
parameters.NumberOfStereoStreams,
parameters.Flags,
mappings));
// Close transfer memory immediately as we don't use it.
context.Device.System.KernelContext.Syscall.CloseHandle(context.Request.HandleDesc.ToCopy[0]);
return ResultCode.Success;
}
[CommandCmif(7)] // 12.0.0+
// GetWorkBufferSizeForMultiStreamEx(buffer<unknown<0x118>, 0x19>) -> u32
public ResultCode GetWorkBufferSizeForMultiStreamEx(ServiceCtx context)
{
ulong parametersAddress = context.Request.PtrBuff[0].Position;
OpusMultiStreamParametersEx parameters = context.Memory.Read<OpusMultiStreamParametersEx>(parametersAddress);
int opusDecoderSize = GetOpusMultistreamDecoderSize(parameters.NumberOfStreams, parameters.NumberOfStereoStreams);
int frameSizeMono48KHz = parameters.Flags.HasFlag(OpusDecoderFlags.LargeFrameSize) ? 5760 : 1920;
int streamSize = BitUtils.AlignUp(parameters.NumberOfStreams * 1500, 64);
int frameSize = BitUtils.AlignUp(parameters.ChannelsCount * frameSizeMono48KHz / (48000 / parameters.SampleRate), 64);
int totalSize = opusDecoderSize + streamSize + frameSize;
context.ResponseData.Write(totalSize);
return ResultCode.Success;
}
[CommandCmif(8)] // 16.0.0+
// GetWorkBufferSizeExEx(OpusParametersEx) -> u32
public ResultCode GetWorkBufferSizeExEx(ServiceCtx context)
{
// NOTE: GetWorkBufferSizeEx use hardcoded values to compute the returned size.
// GetWorkBufferSizeExEx fixes that by using dynamic values.
// Since we're already doing that, it's fine to call it directly.
return GetWorkBufferSizeEx(context);
}
[CommandCmif(9)] // 16.0.0+
// GetWorkBufferSizeForMultiStreamExEx(buffer<unknown<0x118>, 0x19>) -> u32
public ResultCode GetWorkBufferSizeForMultiStreamExEx(ServiceCtx context)
{
// NOTE: GetWorkBufferSizeForMultiStreamEx use hardcoded values to compute the returned size.
// GetWorkBufferSizeForMultiStreamExEx fixes that by using dynamic values.
// Since we're already doing that, it's fine to call it directly.
return GetWorkBufferSizeForMultiStreamEx(context);
}
private static int GetOpusMultistreamDecoderSize(int streams, int coupledStreams)
{
if (streams < 1 || coupledStreams > streams || coupledStreams < 0)
{
return 0;
}
int coupledSize = GetOpusDecoderSize(2);
int monoSize = GetOpusDecoderSize(1);
return Align4(monoSize - GetOpusDecoderAllocSize(1)) * (streams - coupledStreams) +
Align4(coupledSize - GetOpusDecoderAllocSize(2)) * coupledStreams + 0xb90c;
}
private static int Align4(int value)
{
return BitUtils.AlignUp(value, 4);
}
private static int GetOpusDecoderSize(int channelsCount)
{
const int SilkDecoderSize = 0x2160;
if (channelsCount < 1 || channelsCount > 2)
{
return 0;
}
int celtDecoderSize = GetCeltDecoderSize(channelsCount);
int opusDecoderSize = GetOpusDecoderAllocSize(channelsCount) | 0x4c;
return opusDecoderSize + SilkDecoderSize + celtDecoderSize;
}
private static int GetOpusDecoderAllocSize(int channelsCount)
{
return (channelsCount * 0x800 + 0x4803) & -0x800;
}
private static int GetCeltDecoderSize(int channelsCount)
{
const int DecodeBufferSize = 0x2030;
const int Overlap = 120;
const int EBandsCount = 21;
return (DecodeBufferSize + Overlap * 4) * channelsCount + EBandsCount * 16 + 0x50;
}
}
}

View file

@ -1,21 +0,0 @@
namespace Ryujinx.HLE.HOS.Services.Audio
{
enum ResultCode
{
ModuleId = 153,
ErrorCodeShift = 9,
Success = 0,
DeviceNotFound = (1 << ErrorCodeShift) | ModuleId,
UnsupportedRevision = (2 << ErrorCodeShift) | ModuleId,
UnsupportedSampleRate = (3 << ErrorCodeShift) | ModuleId,
BufferSizeTooSmall = (4 << ErrorCodeShift) | ModuleId,
OpusInvalidInput = (6 << ErrorCodeShift) | ModuleId,
TooManyBuffersInUse = (8 << ErrorCodeShift) | ModuleId,
InvalidChannelCount = (10 << ErrorCodeShift) | ModuleId,
InvalidOperation = (513 << ErrorCodeShift) | ModuleId,
InvalidHandle = (1536 << ErrorCodeShift) | ModuleId,
OutputAlreadyStarted = (1540 << ErrorCodeShift) | ModuleId,
}
}

View file

@ -1,23 +0,0 @@
using System;
using System.Buffers.Binary;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.Types
{
[StructLayout(LayoutKind.Sequential)]
struct OpusPacketHeader
{
public uint length;
public uint finalRange;
public static OpusPacketHeader FromSpan(ReadOnlySpan<byte> data)
{
OpusPacketHeader header = MemoryMarshal.Cast<byte, OpusPacketHeader>(data)[0];
header.length = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.length) : header.length;
header.finalRange = BitConverter.IsLittleEndian ? BinaryPrimitives.ReverseEndianness(header.finalRange) : header.finalRange;
return header;
}
}
}

View file

@ -1,15 +0,0 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.HLE.HOS.Services.Audio.Types
{
[StructLayout(LayoutKind.Sequential, Size = 0x10)]
struct OpusParametersEx
{
public int SampleRate;
public int ChannelsCount;
public OpusDecoderFlags Flags;
Array4<byte> Padding1;
}
}

View file

@ -21,7 +21,6 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Concentus" />
<PackageReference Include="LibHac" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
<PackageReference Include="MsgPack.Cli" />
@ -30,11 +29,6 @@
<PackageReference Include="NetCoreServer" />
</ItemGroup>
<!-- Due to Concentus. -->
<PropertyGroup>
<NoWarn>NU1605</NoWarn>
</PropertyGroup>
<ItemGroup>
<None Remove="Homebrew.npdm" />
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />

View file

@ -117,12 +117,12 @@ namespace Ryujinx.HLE
public void SetVolume(float volume)
{
System.SetVolume(Math.Clamp(volume, 0, 1));
AudioDeviceDriver.Volume = Math.Clamp(volume, 0f, 1f);
}
public float GetVolume()
{
return System.GetVolume();
return AudioDeviceDriver.Volume;
}
public void EnableCheats()
@ -132,7 +132,7 @@ namespace Ryujinx.HLE
public bool IsAudioMuted()
{
return System.GetVolume() == 0;
return AudioDeviceDriver.Volume == 0;
}
public void DisposeGpu()

View file

@ -0,0 +1,10 @@
using System;
namespace Ryujinx.Horizon.Common
{
public interface IExternalEvent
{
void Signal();
void Clear();
}
}

View file

@ -1,3 +1,4 @@
using Ryujinx.Memory;
using System;
namespace Ryujinx.Horizon.Common
@ -29,5 +30,9 @@ namespace Ryujinx.Horizon.Common
Result CreatePort(out int serverPortHandle, out int clientPortHandle, int maxSessions, bool isLight, string name);
Result ManageNamedPort(out int handle, string name, int maxSessions);
Result ConnectToPort(out int clientSessionHandle, int clientPortHandle);
IExternalEvent GetExternalEvent(int handle);
IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle);
ulong GetTransferMemoryAddress(int handle);
}
}

View file

@ -36,6 +36,11 @@ namespace Ryujinx.Horizon.Common
ErrorCode = module | (description << ModuleBits);
}
public Result(int errorCode)
{
ErrorCode = errorCode;
}
public readonly override bool Equals(object obj)
{
return obj is Result result && result.Equals(this);

View file

@ -286,13 +286,13 @@ namespace Ryujinx.Horizon.Generators.Hipc
{
if (IsNonSpanOutBuffer(compilation, parameter))
{
generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({outArgIndex++}));");
generator.AppendLine($"using var {argName} = CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}));");
argName = $"out {GenerateSpanCastElement0(canonicalTypeName, $"{argName}.Memory.Span")}";
}
else
{
outParameters.Add(new OutParameter(argName, canonicalTypeName, index, argType));
outParameters.Add(new OutParameter(argName, canonicalTypeName, outArgIndex++, argType));
argName = $"out {canonicalTypeName} {argName}";
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Account
{
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
public readonly record struct Uid
{
public readonly ulong High;

View file

@ -0,0 +1,71 @@
namespace Ryujinx.Horizon.Sdk.Applet
{
enum AppletId : uint
{
None = 0x00,
Application = 0x01,
OverlayApplet = 0x02,
SystemAppletMenu = 0x03,
SystemApplication = 0x04,
LibraryAppletAuth = 0x0A,
LibraryAppletCabinet = 0x0B,
LibraryAppletController = 0x0C,
LibraryAppletDataErase = 0x0D,
LibraryAppletError = 0x0E,
LibraryAppletNetConnect = 0x0F,
LibraryAppletPlayerSelect = 0x10,
LibraryAppletSwkbd = 0x11,
LibraryAppletMiiEdit = 0x12,
LibraryAppletWeb = 0x13,
LibraryAppletShop = 0x14,
LibraryAppletPhotoViewer = 0x15,
LibraryAppletSet = 0x16,
LibraryAppletOfflineWeb = 0x17,
LibraryAppletLoginShare = 0x18,
LibraryAppletWifiWebAuth = 0x19,
LibraryAppletMyPage = 0x1A,
LibraryAppletGift = 0x1B,
LibraryAppletUserMigration = 0x1C,
LibraryAppletPreomiaSys = 0x1D,
LibraryAppletStory = 0x1E,
LibraryAppletPreomiaUsr = 0x1F,
LibraryAppletPreomiaUsrDummy = 0x20,
LibraryAppletSample = 0x21,
LibraryAppletPromoteQualification = 0x22,
LibraryAppletOfflineWebFw17 = 0x32,
LibraryAppletOfflineWeb2Fw17 = 0x33,
LibraryAppletLoginShareFw17 = 0x35,
LibraryAppletLoginShare2Fw17 = 0x36,
LibraryAppletLoginShare3Fw17 = 0x37,
Unknown38 = 0x38,
DevlopmentTool = 0x3E8,
CombinationLA = 0x3F1,
AeSystemApplet = 0x3F2,
AeOverlayApplet = 0x3F3,
AeStarter = 0x3F4,
AeLibraryAppletAlone = 0x3F5,
AeLibraryApplet1 = 0x3F6,
AeLibraryApplet2 = 0x3F7,
AeLibraryApplet3 = 0x3F8,
AeLibraryApplet4 = 0x3F9,
AppletISA = 0x3FA,
AppletIOA = 0x3FB,
AppletISTA = 0x3FC,
AppletILA1 = 0x3FD,
AppletILA2 = 0x3FE,
CombinationLAFw17 = 0x700000DC,
AeSystemAppletFw17 = 0x700000E6,
AeOverlayAppletFw17 = 0x700000E7,
AeStarterFw17 = 0x700000E8,
AeLibraryAppletAloneFw17 = 0x700000E9,
AeLibraryApplet1Fw17 = 0x700000EA,
AeLibraryApplet2Fw17 = 0x700000EB,
AeLibraryApplet3Fw17 = 0x700000EC,
AeLibraryApplet4Fw17 = 0x700000ED,
AppletISAFw17 = 0x700000F0,
AppletIOAFw17 = 0x700000F1,
AppletISTAFw17 = 0x700000F2,
AppletILA1Fw17 = 0x700000F3,
AppletILA2Fw17 = 0x700000F4,
}
}

View file

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Applet
{
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
readonly struct AppletResourceUserId
{
public readonly ulong Id;
public AppletResourceUserId(ulong id)
{
Id = id;
}
}
}

View file

@ -0,0 +1,50 @@
using Ryujinx.Audio.Integration;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.OsTypes;
using System;
namespace Ryujinx.Horizon.Sdk.Audio
{
class AudioEvent : IWritableEvent, IDisposable
{
private SystemEventType _systemEvent;
private readonly IExternalEvent _externalEvent;
public AudioEvent()
{
Os.CreateSystemEvent(out _systemEvent, EventClearMode.ManualClear, interProcess: true);
// We need to do this because the event will be signalled from a different thread.
_externalEvent = HorizonStatic.Syscall.GetExternalEvent(Os.GetWritableHandleOfSystemEvent(ref _systemEvent));
}
public void Signal()
{
_externalEvent.Signal();
}
public void Clear()
{
_externalEvent.Clear();
}
public int GetReadableHandle()
{
return Os.GetReadableHandleOfSystemEvent(ref _systemEvent);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Os.DestroySystemEvent(ref _systemEvent);
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View file

@ -0,0 +1,12 @@
using Ryujinx.Horizon.Common;
namespace Ryujinx.Horizon.Sdk.Audio
{
static class AudioResult
{
private const int ModuleId = 153;
public static Result DeviceNotFound => new(ModuleId, 1);
public static Result UnsupportedRevision => new(ModuleId, 2);
}
}

View file

@ -0,0 +1,252 @@
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.OsTypes;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class AudioDevice : IAudioDevice, IDisposable
{
private readonly VirtualDeviceSessionRegistry _registry;
private readonly VirtualDeviceSession[] _sessions;
private readonly bool _isUsbDeviceSupported;
private SystemEventType _audioEvent;
private SystemEventType _audioInputEvent;
private SystemEventType _audioOutputEvent;
public AudioDevice(VirtualDeviceSessionRegistry registry, AppletResourceUserId appletResourceId, uint revision)
{
_registry = registry;
BehaviourContext behaviourContext = new();
behaviourContext.SetUserRevision((int)revision);
_isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
_sessions = registry.GetSessionByAppletResourceId(appletResourceId.Id);
Os.CreateSystemEvent(out _audioEvent, EventClearMode.AutoClear, interProcess: true);
Os.CreateSystemEvent(out _audioInputEvent, EventClearMode.AutoClear, interProcess: true);
Os.CreateSystemEvent(out _audioOutputEvent, EventClearMode.AutoClear, interProcess: true);
}
private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
{
result = null;
foreach (VirtualDeviceSession session in _sessions)
{
if (session.Device.Name.Equals(name))
{
if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
{
return false;
}
result = session;
return true;
}
}
return false;
}
[CmifCommand(0)]
public Result ListAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names, out int nameCount)
{
int count = 0;
foreach (VirtualDeviceSession session in _sessions)
{
if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
{
continue;
}
if (count >= names.Length)
{
break;
}
names[count] = new DeviceName(session.Device.Name);
count++;
}
nameCount = count;
return Result.Success;
}
[CmifCommand(1)]
public Result SetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, float volume)
{
if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString(), ignoreRevLimitation: true))
{
if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
{
result = _sessions[0];
}
result.Volume = volume;
}
return Result.Success;
}
[CmifCommand(2)]
public Result GetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, out float volume)
{
if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString()))
{
volume = result.Volume;
}
else
{
volume = 0f;
}
return Result.Success;
}
[CmifCommand(3)]
public Result GetActiveAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> name)
{
VirtualDevice device = _registry.ActiveDevice;
if (!_isUsbDeviceSupported && device.IsUsbDevice())
{
device = _registry.DefaultDevice;
}
if (name.Length > 0)
{
name[0] = new DeviceName(device.Name);
}
return Result.Success;
}
[CmifCommand(4)]
public Result QueryAudioDeviceSystemEvent([CopyHandle] out int eventHandle)
{
eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioEvent);
return Result.Success;
}
[CmifCommand(5)]
public Result GetActiveChannelCount(out int channelCount)
{
VirtualDevice device = _registry.ActiveDevice;
if (!_isUsbDeviceSupported && device.IsUsbDevice())
{
device = _registry.DefaultDevice;
}
channelCount = (int)device.ChannelCount;
return Result.Success;
}
[CmifCommand(6)] // 3.0.0+
public Result ListAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names, out int nameCount)
{
return ListAudioDeviceName(names, out nameCount);
}
[CmifCommand(7)] // 3.0.0+
public Result SetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, float volume)
{
return SetAudioDeviceOutputVolume(name, volume);
}
[CmifCommand(8)] // 3.0.0+
public Result GetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, out float volume)
{
return GetAudioDeviceOutputVolume(name, out volume);
}
[CmifCommand(10)] // 3.0.0+
public Result GetActiveAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> name)
{
return GetActiveAudioDeviceName(name);
}
[CmifCommand(11)] // 3.0.0+
public Result QueryAudioDeviceInputEvent([CopyHandle] out int eventHandle)
{
eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioInputEvent);
return Result.Success;
}
[CmifCommand(12)] // 3.0.0+
public Result QueryAudioDeviceOutputEvent([CopyHandle] out int eventHandle)
{
eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioOutputEvent);
return Result.Success;
}
[CmifCommand(13)] // 13.0.0+
public Result GetActiveAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> name)
{
if (name.Length > 0)
{
name[0] = new DeviceName(_registry.ActiveDevice.GetOutputDeviceName());
}
return Result.Success;
}
[CmifCommand(14)] // 13.0.0+
public Result ListAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names, out int nameCount)
{
int count = 0;
foreach (VirtualDeviceSession session in _sessions)
{
if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
{
continue;
}
if (count >= names.Length)
{
break;
}
names[count] = new DeviceName(session.Device.GetOutputDeviceName());
count++;
}
nameCount = count;
return Result.Success;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Os.DestroySystemEvent(ref _audioEvent);
Os.DestroySystemEvent(ref _audioInputEvent);
Os.DestroySystemEvent(ref _audioOutputEvent);
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View file

@ -0,0 +1,171 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Input;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class AudioIn : IAudioIn, IDisposable
{
private readonly AudioInputSystem _impl;
private int _processHandle;
public AudioIn(AudioInputSystem impl, int processHandle)
{
_impl = impl;
_processHandle = processHandle;
}
[CmifCommand(0)]
public Result GetAudioInState(out AudioDeviceState state)
{
state = _impl.GetState();
return Result.Success;
}
[CmifCommand(1)]
public Result Start()
{
return new Result((int)_impl.Start());
}
[CmifCommand(2)]
public Result Stop()
{
return new Result((int)_impl.Stop());
}
[CmifCommand(3)]
public Result AppendAudioInBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer)
{
AudioUserBuffer userBuffer = default;
if (buffer.Length > 0)
{
userBuffer = buffer[0];
}
return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer));
}
[CmifCommand(4)]
public Result RegisterBufferEvent([CopyHandle] out int eventHandle)
{
eventHandle = 0;
if (_impl.RegisterBufferEvent() is AudioEvent audioEvent)
{
eventHandle = audioEvent.GetReadableHandle();
}
return Result.Success;
}
[CmifCommand(5)]
public Result GetReleasedAudioInBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ulong> bufferTags)
{
return new Result((int)_impl.GetReleasedBuffers(bufferTags, out count));
}
[CmifCommand(6)]
public Result ContainsAudioInBuffer(out bool contains, ulong bufferTag)
{
contains = _impl.ContainsBuffer(bufferTag);
return Result.Success;
}
[CmifCommand(7)] // 3.0.0+
public Result AppendUacInBuffer(
ulong bufferTag,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer,
[CopyHandle] int eventHandle)
{
AudioUserBuffer userBuffer = default;
if (buffer.Length > 0)
{
userBuffer = buffer[0];
}
return new Result((int)_impl.AppendUacBuffer(bufferTag, ref userBuffer, (uint)eventHandle));
}
[CmifCommand(8)] // 3.0.0+
public Result AppendAudioInBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer)
{
return AppendAudioInBuffer(bufferTag, buffer);
}
[CmifCommand(9)] // 3.0.0+
public Result GetReleasedAudioInBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<ulong> bufferTags)
{
return GetReleasedAudioInBuffers(out count, bufferTags);
}
[CmifCommand(10)] // 3.0.0+
public Result AppendUacInBufferAuto(
ulong bufferTag,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer,
[CopyHandle] int eventHandle)
{
return AppendUacInBuffer(bufferTag, buffer, eventHandle);
}
[CmifCommand(11)] // 4.0.0+
public Result GetAudioInBufferCount(out uint bufferCount)
{
bufferCount = _impl.GetBufferCount();
return Result.Success;
}
[CmifCommand(12)] // 4.0.0+
public Result SetDeviceGain(float gain)
{
_impl.SetVolume(gain);
return Result.Success;
}
[CmifCommand(13)] // 4.0.0+
public Result GetDeviceGain(out float gain)
{
gain = _impl.GetVolume();
return Result.Success;
}
[CmifCommand(14)] // 6.0.0+
public Result FlushAudioInBuffers(out bool pending)
{
pending = _impl.FlushBuffers();
return Result.Success;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_impl.Dispose();
if (_processHandle != 0)
{
HorizonStatic.Syscall.CloseHandle(_processHandle);
_processHandle = 0;
}
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View file

@ -0,0 +1,130 @@
using Ryujinx.Audio;
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Input;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class AudioInManager : IAudioInManager
{
private readonly AudioInputManager _impl;
public AudioInManager(AudioInputManager impl)
{
_impl = impl;
}
[CmifCommand(0)]
public Result ListAudioIns(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names)
{
string[] deviceNames = _impl.ListAudioIns(filtered: false);
count = 0;
foreach (string deviceName in deviceNames)
{
if (count >= names.Length)
{
break;
}
names[count++] = new DeviceName(deviceName);
}
return Result.Success;
}
[CmifCommand(1)]
public Result OpenAudioIn(
out AudioOutputConfiguration outputConfiguration,
out IAudioIn audioIn,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
AudioInputConfiguration parameter,
AppletResourceUserId appletResourceId,
[CopyHandle] int processHandle,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
[ClientProcessId] ulong pid)
{
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
ResultCode rc = _impl.OpenAudioIn(
out string outputDeviceName,
out outputConfiguration,
out AudioInputSystem inSystem,
clientMemoryManager,
name.Length > 0 ? name[0].ToString() : string.Empty,
SampleFormat.PcmInt16,
ref parameter);
if (rc == ResultCode.Success && outName.Length > 0)
{
outName[0] = new DeviceName(outputDeviceName);
}
audioIn = new AudioIn(inSystem, processHandle);
return new Result((int)rc);
}
[CmifCommand(2)] // 3.0.0+
public Result ListAudioInsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
{
return ListAudioIns(out count, names);
}
[CmifCommand(3)] // 3.0.0+
public Result OpenAudioInAuto(
out AudioOutputConfiguration outputConfig,
out IAudioIn audioIn,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> outName,
AudioInputConfiguration parameter,
AppletResourceUserId appletResourceId,
[CopyHandle] int processHandle,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name,
[ClientProcessId] ulong pid)
{
return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid);
}
[CmifCommand(4)] // 3.0.0+
public Result ListAudioInsAutoFiltered(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
{
string[] deviceNames = _impl.ListAudioIns(filtered: true);
count = 0;
foreach (string deviceName in deviceNames)
{
if (count >= names.Length)
{
break;
}
names[count++] = new DeviceName(deviceName);
}
return Result.Success;
}
[CmifCommand(5)] // 5.0.0+
public Result OpenAudioInProtocolSpecified(
out AudioOutputConfiguration outputConfig,
out IAudioIn audioIn,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
AudioInProtocol protocol,
AudioInputConfiguration parameter,
AppletResourceUserId appletResourceId,
[CopyHandle] int processHandle,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
[ClientProcessId] ulong pid)
{
// NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices).
return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid);
}
}
}

View file

@ -0,0 +1,23 @@
using Ryujinx.Common.Memory;
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)]
struct AudioInProtocol
{
public AudioInProtocolName Name;
public Array7<byte> Padding;
public AudioInProtocol(AudioInProtocolName name)
{
Name = name;
Padding = new();
}
public override readonly string ToString()
{
return Name.ToString();
}
}
}

View file

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

View file

@ -0,0 +1,154 @@
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Output;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class AudioOut : IAudioOut, IDisposable
{
private readonly AudioOutputSystem _impl;
private int _processHandle;
public AudioOut(AudioOutputSystem impl, int processHandle)
{
_impl = impl;
_processHandle = processHandle;
}
[CmifCommand(0)]
public Result GetAudioOutState(out AudioDeviceState state)
{
state = _impl.GetState();
return Result.Success;
}
[CmifCommand(1)]
public Result Start()
{
return new Result((int)_impl.Start());
}
[CmifCommand(2)]
public Result Stop()
{
return new Result((int)_impl.Stop());
}
[CmifCommand(3)]
public Result AppendAudioOutBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer)
{
AudioUserBuffer userBuffer = default;
if (buffer.Length > 0)
{
userBuffer = buffer[0];
}
return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer));
}
[CmifCommand(4)]
public Result RegisterBufferEvent([CopyHandle] out int eventHandle)
{
eventHandle = 0;
if (_impl.RegisterBufferEvent() is AudioEvent audioEvent)
{
eventHandle = audioEvent.GetReadableHandle();
}
return Result.Success;
}
[CmifCommand(5)]
public Result GetReleasedAudioOutBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ulong> bufferTags)
{
return new Result((int)_impl.GetReleasedBuffer(bufferTags, out count));
}
[CmifCommand(6)]
public Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag)
{
contains = _impl.ContainsBuffer(bufferTag);
return Result.Success;
}
[CmifCommand(7)] // 3.0.0+
public Result AppendAudioOutBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer)
{
return AppendAudioOutBuffer(bufferTag, buffer);
}
[CmifCommand(8)] // 3.0.0+
public Result GetReleasedAudioOutBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<ulong> bufferTags)
{
return GetReleasedAudioOutBuffers(out count, bufferTags);
}
[CmifCommand(9)] // 4.0.0+
public Result GetAudioOutBufferCount(out uint bufferCount)
{
bufferCount = _impl.GetBufferCount();
return Result.Success;
}
[CmifCommand(10)] // 4.0.0+
public Result GetAudioOutPlayedSampleCount(out ulong sampleCount)
{
sampleCount = _impl.GetPlayedSampleCount();
return Result.Success;
}
[CmifCommand(11)] // 4.0.0+
public Result FlushAudioOutBuffers(out bool pending)
{
pending = _impl.FlushBuffers();
return Result.Success;
}
[CmifCommand(12)] // 6.0.0+
public Result SetAudioOutVolume(float volume)
{
_impl.SetVolume(volume);
return Result.Success;
}
[CmifCommand(13)] // 6.0.0+
public Result GetAudioOutVolume(out float volume)
{
volume = _impl.GetVolume();
return Result.Success;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_impl.Dispose();
if (_processHandle != 0)
{
HorizonStatic.Syscall.CloseHandle(_processHandle);
_processHandle = 0;
}
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View file

@ -0,0 +1,93 @@
using Ryujinx.Audio;
using Ryujinx.Audio.Common;
using Ryujinx.Audio.Output;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class AudioOutManager : IAudioOutManager
{
private readonly AudioOutputManager _impl;
public AudioOutManager(AudioOutputManager impl)
{
_impl = impl;
}
[CmifCommand(0)]
public Result ListAudioOuts(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names)
{
string[] deviceNames = _impl.ListAudioOuts();
count = 0;
foreach (string deviceName in deviceNames)
{
if (count >= names.Length)
{
break;
}
names[count++] = new DeviceName(deviceName);
}
return Result.Success;
}
[CmifCommand(1)]
public Result OpenAudioOut(
out AudioOutputConfiguration outputConfig,
out IAudioOut audioOut,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
AudioInputConfiguration parameter,
AppletResourceUserId appletResourceId,
[CopyHandle] int processHandle,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
[ClientProcessId] ulong pid)
{
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
ResultCode rc = _impl.OpenAudioOut(
out string outputDeviceName,
out outputConfig,
out AudioOutputSystem outSystem,
clientMemoryManager,
name.Length > 0 ? name[0].ToString() : string.Empty,
SampleFormat.PcmInt16,
ref parameter);
if (rc == ResultCode.Success && outName.Length > 0)
{
outName[0] = new DeviceName(outputDeviceName);
}
audioOut = new AudioOut(outSystem, processHandle);
return new Result((int)rc);
}
[CmifCommand(2)] // 3.0.0+
public Result ListAudioOutsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
{
return ListAudioOuts(out count, names);
}
[CmifCommand(3)] // 3.0.0+
public Result OpenAudioOutAuto(
out AudioOutputConfiguration outputConfig,
out IAudioOut audioOut,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> outName,
AudioInputConfiguration parameter,
AppletResourceUserId appletResourceId,
[CopyHandle] int processHandle,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name,
[ClientProcessId] ulong pid)
{
return OpenAudioOut(out outputConfig, out audioOut, outName, parameter, appletResourceId, processHandle, name, pid);
}
}
}

View file

@ -0,0 +1,187 @@
using Ryujinx.Audio;
using Ryujinx.Audio.Integration;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Common.Memory;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
using System.Buffers;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class AudioRenderer : IAudioRenderer, IDisposable
{
private readonly AudioRenderSystem _renderSystem;
private int _workBufferHandle;
private int _processHandle;
public AudioRenderer(AudioRenderSystem renderSystem, int workBufferHandle, int processHandle)
{
_renderSystem = renderSystem;
_workBufferHandle = workBufferHandle;
_processHandle = processHandle;
}
[CmifCommand(0)]
public Result GetSampleRate(out int sampleRate)
{
sampleRate = (int)_renderSystem.GetSampleRate();
return Result.Success;
}
[CmifCommand(1)]
public Result GetSampleCount(out int sampleCount)
{
sampleCount = (int)_renderSystem.GetSampleCount();
return Result.Success;
}
[CmifCommand(2)]
public Result GetMixBufferCount(out int mixBufferCount)
{
mixBufferCount = (int)_renderSystem.GetMixBufferCount();
return Result.Success;
}
[CmifCommand(3)]
public Result GetState(out int state)
{
state = _renderSystem.IsActive() ? 0 : 1;
return Result.Success;
}
[CmifCommand(4)]
public Result RequestUpdate(
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> performanceOutput,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
{
using IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(output.Length);
using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length);
Memory<byte> outputMemory = outputOwner.Memory;
Memory<byte> performanceOutputMemory = performanceOutputOwner.Memory;
using MemoryHandle outputHandle = outputMemory.Pin();
using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin();
Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray()));
outputMemory.Span.CopyTo(output);
performanceOutputMemory.Span.CopyTo(performanceOutput);
return result;
}
[CmifCommand(5)]
public Result Start()
{
_renderSystem.Start();
return Result.Success;
}
[CmifCommand(6)]
public Result Stop()
{
_renderSystem.Stop();
return Result.Success;
}
[CmifCommand(7)]
public Result QuerySystemEvent([CopyHandle] out int eventHandle)
{
ResultCode rc = _renderSystem.QuerySystemEvent(out IWritableEvent systemEvent);
eventHandle = 0;
if (rc == ResultCode.Success && systemEvent is AudioEvent audioEvent)
{
eventHandle = audioEvent.GetReadableHandle();
}
return new Result((int)rc);
}
[CmifCommand(8)]
public Result SetRenderingTimeLimit(int percent)
{
_renderSystem.SetRenderingTimeLimitPercent((uint)percent);
return Result.Success;
}
[CmifCommand(9)]
public Result GetRenderingTimeLimit(out int percent)
{
percent = (int)_renderSystem.GetRenderingTimeLimit();
return Result.Success;
}
[CmifCommand(10)] // 3.0.0+
public Result RequestUpdateAuto(
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> output,
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> performanceOutput,
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> input)
{
return RequestUpdate(output, performanceOutput, input);
}
[CmifCommand(11)] // 3.0.0+
public Result ExecuteAudioRendererRendering()
{
return new Result((int)_renderSystem.ExecuteAudioRendererRendering());
}
[CmifCommand(12)] // 15.0.0+
public Result SetVoiceDropParameter(float voiceDropParameter)
{
_renderSystem.SetVoiceDropParameter(voiceDropParameter);
return Result.Success;
}
[CmifCommand(13)] // 15.0.0+
public Result GetVoiceDropParameter(out float voiceDropParameter)
{
voiceDropParameter = _renderSystem.GetVoiceDropParameter();
return Result.Success;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_renderSystem.Dispose();
if (_workBufferHandle != 0)
{
HorizonStatic.Syscall.CloseHandle(_workBufferHandle);
_workBufferHandle = 0;
}
if (_processHandle != 0)
{
HorizonStatic.Syscall.CloseHandle(_processHandle);
_processHandle = 0;
}
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View file

@ -0,0 +1,132 @@
using Ryujinx.Audio.Renderer.Device;
using Ryujinx.Audio.Renderer.Server;
using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class AudioRendererManager : IAudioRendererManager
{
private const uint InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24);
private readonly Ryujinx.Audio.Renderer.Server.AudioRendererManager _impl;
private readonly VirtualDeviceSessionRegistry _registry;
public AudioRendererManager(Ryujinx.Audio.Renderer.Server.AudioRendererManager impl, VirtualDeviceSessionRegistry registry)
{
_impl = impl;
_registry = registry;
}
[CmifCommand(0)]
public Result OpenAudioRenderer(
out IAudioRenderer renderer,
AudioRendererParameterInternal parameter,
[CopyHandle] int workBufferHandle,
[CopyHandle] int processHandle,
ulong workBufferSize,
AppletResourceUserId appletResourceId,
[ClientProcessId] ulong pid)
{
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
ulong workBufferAddress = HorizonStatic.Syscall.GetTransferMemoryAddress(workBufferHandle);
Result result = new Result((int)_impl.OpenAudioRenderer(
out var renderSystem,
clientMemoryManager,
ref parameter.Configuration,
appletResourceId.Id,
workBufferAddress,
workBufferSize,
(uint)processHandle));
if (result.IsSuccess)
{
renderer = new AudioRenderer(renderSystem, workBufferHandle, processHandle);
}
else
{
renderer = null;
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
HorizonStatic.Syscall.CloseHandle(processHandle);
}
return result;
}
[CmifCommand(1)]
public Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter)
{
if (BehaviourContext.CheckValidRevision(parameter.Configuration.Revision))
{
workBufferSize = (long)Ryujinx.Audio.Renderer.Server.AudioRendererManager.GetWorkBufferSize(ref parameter.Configuration);
Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{workBufferSize:x16}.");
return Result.Success;
}
else
{
workBufferSize = 0;
Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Configuration.Revision)} is not supported!");
return AudioResult.UnsupportedRevision;
}
}
[CmifCommand(2)]
public Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId)
{
audioDevice = new AudioDevice(_registry, appletResourceId, InitialRevision);
return Result.Success;
}
[CmifCommand(3)] // 3.0.0+
public Result OpenAudioRendererForManualExecution(
out IAudioRenderer renderer,
AudioRendererParameterInternal parameter,
ulong workBufferAddress,
[CopyHandle] int processHandle,
ulong workBufferSize,
AppletResourceUserId appletResourceId,
[ClientProcessId] ulong pid)
{
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
Result result = new Result((int)_impl.OpenAudioRenderer(
out var renderSystem,
clientMemoryManager,
ref parameter.Configuration,
appletResourceId.Id,
workBufferAddress,
workBufferSize,
(uint)processHandle));
if (result.IsSuccess)
{
renderer = new AudioRenderer(renderSystem, 0, processHandle);
}
else
{
renderer = null;
HorizonStatic.Syscall.CloseHandle(processHandle);
}
return result;
}
[CmifCommand(4)] // 4.0.0+
public Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId, uint revision)
{
audioDevice = new AudioDevice(_registry, appletResourceId, revision);
return Result.Success;
}
}
}

View file

@ -0,0 +1,14 @@
using Ryujinx.Audio.Renderer.Parameter;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
struct AudioRendererParameterInternal
{
public AudioRendererConfiguration Configuration;
public AudioRendererParameterInternal(AudioRendererConfiguration configuration)
{
Configuration = configuration;
}
}
}

Some files were not shown because too many files have changed in this diff Show more