mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2024-11-24 07:24:15 +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:
parent
57d8afd0c9
commit
d4d0a48bfe
130 changed files with 3096 additions and 3174 deletions
|
@ -20,6 +20,25 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
private bool _stillRunning;
|
private bool _stillRunning;
|
||||||
private readonly Thread _updaterThread;
|
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()
|
public OpenALHardwareDeviceDriver()
|
||||||
{
|
{
|
||||||
_device = ALC.OpenDevice("");
|
_device = ALC.OpenDevice("");
|
||||||
|
@ -34,6 +53,8 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
Name = "HardwareDeviceDriver.OpenAL",
|
Name = "HardwareDeviceDriver.OpenAL",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_volume = 1f;
|
||||||
|
|
||||||
_updaterThread.Start();
|
_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)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
|
@ -73,7 +94,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
throw new ArgumentException($"{channelCount}");
|
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);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
|
|
@ -16,10 +16,11 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
private bool _isActive;
|
private bool _isActive;
|
||||||
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
|
private readonly Queue<OpenALAudioBuffer> _queuedBuffers;
|
||||||
private ulong _playedSampleCount;
|
private ulong _playedSampleCount;
|
||||||
|
private float _volume;
|
||||||
|
|
||||||
private readonly object _lock = new();
|
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;
|
_driver = driver;
|
||||||
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
_queuedBuffers = new Queue<OpenALAudioBuffer>();
|
||||||
|
@ -27,7 +28,7 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
_targetFormat = GetALFormat();
|
_targetFormat = GetALFormat();
|
||||||
_isActive = false;
|
_isActive = false;
|
||||||
_playedSampleCount = 0;
|
_playedSampleCount = 0;
|
||||||
SetVolume(requestedVolume);
|
SetVolume(1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ALFormat GetALFormat()
|
private ALFormat GetALFormat()
|
||||||
|
@ -85,17 +86,22 @@ namespace Ryujinx.Audio.Backends.OpenAL
|
||||||
|
|
||||||
public override void SetVolume(float volume)
|
public override void SetVolume(float volume)
|
||||||
{
|
{
|
||||||
lock (_lock)
|
_volume = volume;
|
||||||
{
|
|
||||||
AL.Source(_sourceId, ALSourcef.Gain, volume);
|
UpdateMasterVolume(_driver.Volume);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override float GetVolume()
|
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()
|
public override void Start()
|
||||||
|
|
|
@ -20,6 +20,8 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
|
|
||||||
private readonly bool _supportSurroundConfiguration;
|
private readonly bool _supportSurroundConfiguration;
|
||||||
|
|
||||||
|
public float Volume { get; set; }
|
||||||
|
|
||||||
// TODO: Add this to SDL2-CS
|
// TODO: Add this to SDL2-CS
|
||||||
// NOTE: We use a DllImport here because of marshaling issue for spec.
|
// NOTE: We use a DllImport here because of marshaling issue for spec.
|
||||||
#pragma warning disable SYSLIB1054
|
#pragma warning disable SYSLIB1054
|
||||||
|
@ -48,6 +50,8 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
{
|
{
|
||||||
_supportSurroundConfiguration = spec.channels >= 6;
|
_supportSurroundConfiguration = spec.channels >= 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Volume = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsSupported => IsSupportedInternal();
|
public static bool IsSupported => IsSupportedInternal();
|
||||||
|
@ -74,7 +78,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
return _pauseEvent;
|
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)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
|
@ -91,7 +95,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
throw new NotImplementedException("Input direction is currently not implemented on SDL2 backend!");
|
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);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
private float _volume;
|
private float _volume;
|
||||||
private readonly ushort _nativeSampleFormat;
|
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;
|
_driver = driver;
|
||||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||||
|
@ -37,7 +37,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
_nativeSampleFormat = SDL2HardwareDeviceDriver.GetSDL2Format(RequestedSampleFormat);
|
||||||
_sampleCount = uint.MaxValue;
|
_sampleCount = uint.MaxValue;
|
||||||
_started = false;
|
_started = false;
|
||||||
_volume = requestedVolume;
|
_volume = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
private void EnsureAudioStreamSetup(AudioBuffer buffer)
|
||||||
|
@ -99,7 +99,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||||
streamSpan.Clear();
|
streamSpan.Clear();
|
||||||
|
|
||||||
// Apply volume to written data
|
// 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);
|
ulong sampleCount = GetSampleCount(samples.Length);
|
||||||
|
|
|
@ -19,6 +19,25 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
||||||
private int _disposeState;
|
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()
|
public SoundIoHardwareDeviceDriver()
|
||||||
{
|
{
|
||||||
_audioContext = SoundIoContext.Create();
|
_audioContext = SoundIoContext.Create();
|
||||||
|
@ -122,7 +141,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
return _pauseEvent;
|
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)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
|
@ -134,14 +153,12 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
sampleRate = Constants.TargetSampleRate;
|
sampleRate = Constants.TargetSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
volume = Math.Clamp(volume, 0, 1);
|
|
||||||
|
|
||||||
if (direction != Direction.Output)
|
if (direction != Direction.Output)
|
||||||
{
|
{
|
||||||
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
|
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);
|
_sessions.TryAdd(session, 0);
|
||||||
|
|
||||||
|
|
|
@ -18,16 +18,18 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
private readonly DynamicRingBuffer _ringBuffer;
|
private readonly DynamicRingBuffer _ringBuffer;
|
||||||
private ulong _playedSampleCount;
|
private ulong _playedSampleCount;
|
||||||
private readonly ManualResetEvent _updateRequiredEvent;
|
private readonly ManualResetEvent _updateRequiredEvent;
|
||||||
|
private float _volume;
|
||||||
private int _disposeState;
|
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;
|
_driver = driver;
|
||||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||||
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
||||||
_ringBuffer = new DynamicRingBuffer();
|
_ringBuffer = new DynamicRingBuffer();
|
||||||
|
_volume = 1f;
|
||||||
|
|
||||||
SetupOutputStream(requestedVolume);
|
SetupOutputStream(driver.Volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetupOutputStream(float requestedVolume)
|
private void SetupOutputStream(float requestedVolume)
|
||||||
|
@ -47,7 +49,7 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
|
|
||||||
public override float GetVolume()
|
public override float GetVolume()
|
||||||
{
|
{
|
||||||
return _outputStream.Volume;
|
return _volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void PrepareToClose() { }
|
public override void PrepareToClose() { }
|
||||||
|
@ -63,7 +65,14 @@ namespace Ryujinx.Audio.Backends.SoundIo
|
||||||
|
|
||||||
public override void SetVolume(float volume)
|
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()
|
public override void Start()
|
||||||
|
|
|
@ -16,6 +16,12 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||||
|
|
||||||
public static bool IsSupported => true;
|
public static bool IsSupported => true;
|
||||||
|
|
||||||
|
public float Volume
|
||||||
|
{
|
||||||
|
get => _realDriver.Volume;
|
||||||
|
set => _realDriver.Volume = value;
|
||||||
|
}
|
||||||
|
|
||||||
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
|
public CompatLayerHardwareDeviceDriver(IHardwareDeviceDriver realDevice)
|
||||||
{
|
{
|
||||||
_realDriver = realDevice;
|
_realDriver = realDevice;
|
||||||
|
@ -90,7 +96,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||||
throw new ArgumentException("No valid sample format configuration found!");
|
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)
|
if (channelCount == 0)
|
||||||
{
|
{
|
||||||
|
@ -102,8 +108,6 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||||
sampleRate = Constants.TargetSampleRate;
|
sampleRate = Constants.TargetSampleRate;
|
||||||
}
|
}
|
||||||
|
|
||||||
volume = Math.Clamp(volume, 0, 1);
|
|
||||||
|
|
||||||
if (!_realDriver.SupportsDirection(direction))
|
if (!_realDriver.SupportsDirection(direction))
|
||||||
{
|
{
|
||||||
if (direction == Direction.Input)
|
if (direction == Direction.Input)
|
||||||
|
@ -119,7 +123,7 @@ namespace Ryujinx.Audio.Backends.CompatLayer
|
||||||
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
|
SampleFormat hardwareSampleFormat = SelectHardwareSampleFormat(sampleFormat);
|
||||||
uint hardwareChannelCount = SelectHardwareChannelCount(channelCount);
|
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)
|
if (hardwareChannelCount == channelCount && hardwareSampleFormat == sampleFormat)
|
||||||
{
|
{
|
||||||
|
|
|
@ -14,13 +14,17 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||||
|
|
||||||
public static bool IsSupported => true;
|
public static bool IsSupported => true;
|
||||||
|
|
||||||
|
public float Volume { get; set; }
|
||||||
|
|
||||||
public DummyHardwareDeviceDriver()
|
public DummyHardwareDeviceDriver()
|
||||||
{
|
{
|
||||||
_updateRequiredEvent = new ManualResetEvent(false);
|
_updateRequiredEvent = new ManualResetEvent(false);
|
||||||
_pauseEvent = new ManualResetEvent(true);
|
_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)
|
if (sampleRate == 0)
|
||||||
{
|
{
|
||||||
|
@ -34,7 +38,7 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||||
|
|
||||||
if (direction == Direction.Output)
|
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);
|
return new DummyHardwareDeviceSessionInput(this, memoryManager);
|
||||||
|
|
|
@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Backends.Dummy
|
||||||
|
|
||||||
private ulong _playedSampleCount;
|
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;
|
_manager = manager;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -166,7 +166,6 @@ namespace Ryujinx.Audio.Input
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="filtered">If true, filter disconnected devices</param>
|
/// <param name="filtered">If true, filter disconnected devices</param>
|
||||||
/// <returns>The list of all audio inputs name</returns>
|
/// <returns>The list of all audio inputs name</returns>
|
||||||
#pragma warning disable CA1822 // Mark member as static
|
|
||||||
public string[] ListAudioIns(bool filtered)
|
public string[] ListAudioIns(bool filtered)
|
||||||
{
|
{
|
||||||
if (filtered)
|
if (filtered)
|
||||||
|
@ -176,7 +175,6 @@ namespace Ryujinx.Audio.Input
|
||||||
|
|
||||||
return new[] { Constants.DefaultDeviceInputName };
|
return new[] { Constants.DefaultDeviceInputName };
|
||||||
}
|
}
|
||||||
#pragma warning restore CA1822
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open a new <see cref="AudioInputSystem"/>.
|
/// 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="inputDeviceName">The input device name wanted by the user</param>
|
||||||
/// <param name="sampleFormat">The sample format to use</param>
|
/// <param name="sampleFormat">The sample format to use</param>
|
||||||
/// <param name="parameter">The user configuration</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>
|
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
|
||||||
public ResultCode OpenAudioIn(out string outputDeviceName,
|
public ResultCode OpenAudioIn(out string outputDeviceName,
|
||||||
out AudioOutputConfiguration outputConfiguration,
|
out AudioOutputConfiguration outputConfiguration,
|
||||||
|
@ -197,9 +193,7 @@ namespace Ryujinx.Audio.Input
|
||||||
IVirtualMemoryManager memoryManager,
|
IVirtualMemoryManager memoryManager,
|
||||||
string inputDeviceName,
|
string inputDeviceName,
|
||||||
SampleFormat sampleFormat,
|
SampleFormat sampleFormat,
|
||||||
ref AudioInputConfiguration parameter,
|
ref AudioInputConfiguration parameter)
|
||||||
ulong appletResourceUserId,
|
|
||||||
uint processHandle)
|
|
||||||
{
|
{
|
||||||
int sessionId = AcquireSessionId();
|
int sessionId = AcquireSessionId();
|
||||||
|
|
||||||
|
|
|
@ -13,9 +13,9 @@ namespace Ryujinx.Audio.Integration
|
||||||
|
|
||||||
private readonly byte[] _buffer;
|
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;
|
_channelCount = channelCount;
|
||||||
_sampleRate = sampleRate;
|
_sampleRate = sampleRate;
|
||||||
_currentBufferTag = 0;
|
_currentBufferTag = 0;
|
||||||
|
|
|
@ -16,7 +16,9 @@ namespace Ryujinx.Audio.Integration
|
||||||
Output,
|
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 GetUpdateRequiredEvent();
|
||||||
ManualResetEvent GetPauseEvent();
|
ManualResetEvent GetPauseEvent();
|
||||||
|
|
|
@ -165,12 +165,10 @@ namespace Ryujinx.Audio.Output
|
||||||
/// Get the list of all audio outputs name.
|
/// Get the list of all audio outputs name.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>The list of all audio outputs name</returns>
|
/// <returns>The list of all audio outputs name</returns>
|
||||||
#pragma warning disable CA1822 // Mark member as static
|
|
||||||
public string[] ListAudioOuts()
|
public string[] ListAudioOuts()
|
||||||
{
|
{
|
||||||
return new[] { Constants.DefaultDeviceOutputName };
|
return new[] { Constants.DefaultDeviceOutputName };
|
||||||
}
|
}
|
||||||
#pragma warning restore CA1822
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Open a new <see cref="AudioOutputSystem"/>.
|
/// 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="inputDeviceName">The input device name wanted by the user</param>
|
||||||
/// <param name="sampleFormat">The sample format to use</param>
|
/// <param name="sampleFormat">The sample format to use</param>
|
||||||
/// <param name="parameter">The user configuration</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>
|
/// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns>
|
||||||
public ResultCode OpenAudioOut(out string outputDeviceName,
|
public ResultCode OpenAudioOut(out string outputDeviceName,
|
||||||
out AudioOutputConfiguration outputConfiguration,
|
out AudioOutputConfiguration outputConfiguration,
|
||||||
|
@ -192,16 +187,13 @@ namespace Ryujinx.Audio.Output
|
||||||
IVirtualMemoryManager memoryManager,
|
IVirtualMemoryManager memoryManager,
|
||||||
string inputDeviceName,
|
string inputDeviceName,
|
||||||
SampleFormat sampleFormat,
|
SampleFormat sampleFormat,
|
||||||
ref AudioInputConfiguration parameter,
|
ref AudioInputConfiguration parameter)
|
||||||
ulong appletResourceUserId,
|
|
||||||
uint processHandle,
|
|
||||||
float volume)
|
|
||||||
{
|
{
|
||||||
int sessionId = AcquireSessionId();
|
int sessionId = AcquireSessionId();
|
||||||
|
|
||||||
_sessionsBufferEvents[sessionId].Clear();
|
_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]);
|
AudioOutputSystem audioOut = new(this, _lock, deviceSession, _sessionsBufferEvents[sessionId]);
|
||||||
|
|
||||||
|
@ -234,41 +226,6 @@ namespace Ryujinx.Audio.Output
|
||||||
return result;
|
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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
|
|
@ -45,7 +45,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
_event = new ManualResetEvent(false);
|
_event = new ManualResetEvent(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma warning disable IDE0051 // Remove unused private member
|
|
||||||
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
|
private static uint GetHardwareChannelCount(IHardwareDeviceDriver deviceDriver)
|
||||||
{
|
{
|
||||||
// Get the real device driver (In case the compat layer is on top of it).
|
// 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.
|
// NOTE: We default to stereo as this will get downmixed to mono by the compat layer if it's not compatible.
|
||||||
return 2;
|
return 2;
|
||||||
}
|
}
|
||||||
#pragma warning restore IDE0051
|
|
||||||
|
|
||||||
public void Start(IHardwareDeviceDriver deviceDriver, float volume)
|
public void Start(IHardwareDeviceDriver deviceDriver)
|
||||||
{
|
{
|
||||||
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
OutputDevices = new IHardwareDevice[Constants.AudioRendererSessionCountMax];
|
||||||
|
|
||||||
|
@ -70,7 +68,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
for (int i = 0; i < OutputDevices.Length; i++)
|
for (int i = 0; i < OutputDevices.Length; i++)
|
||||||
{
|
{
|
||||||
// TODO: Don't hardcode sample rate.
|
// 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>();
|
_mailbox = new Mailbox<MailboxMessage>();
|
||||||
|
@ -231,33 +229,6 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
_mailbox.SendResponse(MailboxMessage.Stop);
|
_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()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
@ -269,6 +240,7 @@ namespace Ryujinx.Audio.Renderer.Dsp
|
||||||
if (disposing)
|
if (disposing)
|
||||||
{
|
{
|
||||||
_event.Dispose();
|
_event.Dispose();
|
||||||
|
_mailbox?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -177,12 +177,12 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
/// Start the <see cref="AudioProcessor"/> and worker thread.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void StartLocked(float volume)
|
private void StartLocked()
|
||||||
{
|
{
|
||||||
_isRunning = true;
|
_isRunning = true;
|
||||||
|
|
||||||
// TODO: virtual device mapping (IAudioDevice)
|
// TODO: virtual device mapping (IAudioDevice)
|
||||||
Processor.Start(_deviceDriver, volume);
|
Processor.Start(_deviceDriver);
|
||||||
|
|
||||||
_workerThread = new Thread(SendCommands)
|
_workerThread = new Thread(SendCommands)
|
||||||
{
|
{
|
||||||
|
@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
/// Register a new <see cref="AudioRenderSystem"/>.
|
/// Register a new <see cref="AudioRenderSystem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
/// <param name="renderer">The <see cref="AudioRenderSystem"/> to register.</param>
|
||||||
private void Register(AudioRenderSystem renderer, float volume)
|
private void Register(AudioRenderSystem renderer)
|
||||||
{
|
{
|
||||||
lock (_sessionLock)
|
lock (_sessionLock)
|
||||||
{
|
{
|
||||||
|
@ -265,7 +265,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
if (!_isRunning)
|
if (!_isRunning)
|
||||||
{
|
{
|
||||||
StartLocked(volume);
|
StartLocked();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -312,8 +312,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
ulong appletResourceUserId,
|
ulong appletResourceUserId,
|
||||||
ulong workBufferAddress,
|
ulong workBufferAddress,
|
||||||
ulong workBufferSize,
|
ulong workBufferSize,
|
||||||
uint processHandle,
|
uint processHandle)
|
||||||
float volume)
|
|
||||||
{
|
{
|
||||||
int sessionId = AcquireSessionId();
|
int sessionId = AcquireSessionId();
|
||||||
|
|
||||||
|
@ -338,7 +337,7 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
{
|
{
|
||||||
renderer = audioRenderer;
|
renderer = audioRenderer;
|
||||||
|
|
||||||
Register(renderer, volume);
|
Register(renderer);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -350,21 +349,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetVolume()
|
|
||||||
{
|
|
||||||
if (Processor != null)
|
|
||||||
{
|
|
||||||
return Processor.GetVolume();
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetVolume(float volume)
|
|
||||||
{
|
|
||||||
Processor?.SetVolume(volume);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
|
|
|
@ -4,12 +4,6 @@ using LibHac.Fs;
|
||||||
using LibHac.Fs.Shim;
|
using LibHac.Fs.Shim;
|
||||||
using LibHac.FsSystem;
|
using LibHac.FsSystem;
|
||||||
using LibHac.Tools.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.Cpu;
|
||||||
using Ryujinx.HLE.FileSystem;
|
using Ryujinx.HLE.FileSystem;
|
||||||
using Ryujinx.HLE.HOS.Kernel;
|
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.Account.Acc;
|
||||||
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
using Ryujinx.HLE.HOS.Services.Am.AppletAE.AllSystemAppletProxiesService.SystemAppletProxy;
|
||||||
using Ryujinx.HLE.HOS.Services.Apm;
|
using Ryujinx.HLE.HOS.Services.Apm;
|
||||||
using Ryujinx.HLE.HOS.Services.Audio.AudioRenderer;
|
|
||||||
using Ryujinx.HLE.HOS.Services.Caps;
|
using Ryujinx.HLE.HOS.Services.Caps;
|
||||||
using Ryujinx.HLE.HOS.Services.Mii;
|
using Ryujinx.HLE.HOS.Services.Mii;
|
||||||
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
using Ryujinx.HLE.HOS.Services.Nfc.Nfp.NfpManager;
|
||||||
|
@ -61,11 +54,6 @@ namespace Ryujinx.HLE.HOS
|
||||||
internal ITickSource TickSource { get; }
|
internal ITickSource TickSource { get; }
|
||||||
|
|
||||||
internal SurfaceFlinger SurfaceFlinger { get; private set; }
|
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; }
|
public SystemStateMgr State { get; private set; }
|
||||||
|
|
||||||
|
@ -79,8 +67,6 @@ namespace Ryujinx.HLE.HOS
|
||||||
|
|
||||||
internal ServerBase SmServer { get; private set; }
|
internal ServerBase SmServer { get; private set; }
|
||||||
internal ServerBase BsdServer { 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 FsServer { get; private set; }
|
||||||
internal ServerBase HidServer { get; private set; }
|
internal ServerBase HidServer { get; private set; }
|
||||||
internal ServerBase NvDrvServer { get; private set; }
|
internal ServerBase NvDrvServer { get; private set; }
|
||||||
|
@ -248,56 +234,6 @@ namespace Ryujinx.HLE.HOS
|
||||||
HostSyncpoint = new NvHostSyncpt(device);
|
HostSyncpoint = new NvHostSyncpt(device);
|
||||||
|
|
||||||
SurfaceFlinger = new SurfaceFlinger(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()
|
public void InitializeServices()
|
||||||
|
@ -310,8 +246,6 @@ namespace Ryujinx.HLE.HOS
|
||||||
SmServer.InitDone.WaitOne();
|
SmServer.InitDone.WaitOne();
|
||||||
|
|
||||||
BsdServer = new ServerBase(KernelContext, "BsdServer");
|
BsdServer = new ServerBase(KernelContext, "BsdServer");
|
||||||
AudRenServer = new ServerBase(KernelContext, "AudioRendererServer");
|
|
||||||
AudOutServer = new ServerBase(KernelContext, "AudioOutServer");
|
|
||||||
FsServer = new ServerBase(KernelContext, "FsServer");
|
FsServer = new ServerBase(KernelContext, "FsServer");
|
||||||
HidServer = new ServerBase(KernelContext, "HidServer");
|
HidServer = new ServerBase(KernelContext, "HidServer");
|
||||||
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
|
NvDrvServer = new ServerBase(KernelContext, "NvservicesServer");
|
||||||
|
@ -329,7 +263,13 @@ namespace Ryujinx.HLE.HOS
|
||||||
HorizonFsClient fsClient = new(this);
|
HorizonFsClient fsClient = new(this);
|
||||||
|
|
||||||
ServiceTable = new ServiceTable();
|
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)
|
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()
|
public void ReturnFocus()
|
||||||
{
|
{
|
||||||
AppletState.SetFocus(true);
|
AppletState.SetFocus(true);
|
||||||
|
@ -458,11 +387,7 @@ namespace Ryujinx.HLE.HOS
|
||||||
// "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
|
// "Soft" stops AudioRenderer and AudioManager to avoid some sound between resume and stop.
|
||||||
if (IsPaused)
|
if (IsPaused)
|
||||||
{
|
{
|
||||||
AudioManager.StopUpdates();
|
|
||||||
|
|
||||||
TogglePauseEmulation(false);
|
TogglePauseEmulation(false);
|
||||||
|
|
||||||
AudioRendererManager.StopSendingCommands();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KProcess terminationProcess = new(KernelContext);
|
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.
|
// 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();
|
INvDrvServices.Destroy();
|
||||||
|
|
||||||
AudioManager.Dispose();
|
|
||||||
AudioOutputManager.Dispose();
|
|
||||||
AudioInputManager.Dispose();
|
|
||||||
|
|
||||||
AudioRendererManager.Dispose();
|
|
||||||
|
|
||||||
if (LibHacHorizonManager.ApplicationClient != null)
|
if (LibHacHorizonManager.ApplicationClient != null)
|
||||||
{
|
{
|
||||||
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
|
LibHacHorizonManager.PmClient.Fs.UnregisterProgram(LibHacHorizonManager.ApplicationClient.Os.GetCurrentProcessId().Value).ThrowIfFailure();
|
||||||
|
|
25
src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs
Normal file
25
src/Ryujinx.HLE/HOS/Kernel/SupervisorCall/ExternalEvent.cs
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,6 +8,7 @@ using Ryujinx.HLE.HOS.Kernel.Memory;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Process;
|
using Ryujinx.HLE.HOS.Kernel.Process;
|
||||||
using Ryujinx.HLE.HOS.Kernel.Threading;
|
using Ryujinx.HLE.HOS.Kernel.Threading;
|
||||||
using Ryujinx.Horizon.Common;
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
using System.Buffers;
|
using System.Buffers;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
@ -3142,6 +3143,37 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall
|
||||||
}
|
}
|
||||||
#pragma warning restore CA1822
|
#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)
|
private static bool IsPointingInsideKernel(ulong address)
|
||||||
{
|
{
|
||||||
return (address + 0x1000000000) < 0xffffff000;
|
return (address + 0x1000000000) < 0xffffff000;
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audctl")]
|
|
||||||
class IAudioController : IpcService
|
|
||||||
{
|
|
||||||
public IAudioController(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audin:a")]
|
|
||||||
class IAudioInManagerForApplet : IpcService
|
|
||||||
{
|
|
||||||
public IAudioInManagerForApplet(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audin:d")]
|
|
||||||
class IAudioInManagerForDebugger : IpcService
|
|
||||||
{
|
|
||||||
public IAudioInManagerForDebugger(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audout:a")]
|
|
||||||
class IAudioOutManagerForApplet : IpcService
|
|
||||||
{
|
|
||||||
public IAudioOutManagerForApplet(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audout:d")]
|
|
||||||
class IAudioOutManagerForDebugger : IpcService
|
|
||||||
{
|
|
||||||
public IAudioOutManagerForDebugger(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audren:a")]
|
|
||||||
class IAudioRendererManagerForApplet : IpcService
|
|
||||||
{
|
|
||||||
public IAudioRendererManagerForApplet(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audren:d")]
|
|
||||||
class IAudioRendererManagerForDebugger : IpcService
|
|
||||||
{
|
|
||||||
public IAudioRendererManagerForDebugger(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("auddev")] // 6.0.0+
|
|
||||||
class IAudioSnoopManager : IpcService
|
|
||||||
{
|
|
||||||
public IAudioSnoopManager(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audrec:u")]
|
|
||||||
class IFinalOutputRecorderManager : IpcService
|
|
||||||
{
|
|
||||||
public IFinalOutputRecorderManager(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audrec:a")]
|
|
||||||
class IFinalOutputRecorderManagerForApplet : IpcService
|
|
||||||
{
|
|
||||||
public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,8 +0,0 @@
|
||||||
namespace Ryujinx.HLE.HOS.Services.Audio
|
|
||||||
{
|
|
||||||
[Service("audrec:d")]
|
|
||||||
class IFinalOutputRecorderManagerForDebugger : IpcService
|
|
||||||
{
|
|
||||||
public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,7 +21,6 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Concentus" />
|
|
||||||
<PackageReference Include="LibHac" />
|
<PackageReference Include="LibHac" />
|
||||||
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" />
|
||||||
<PackageReference Include="MsgPack.Cli" />
|
<PackageReference Include="MsgPack.Cli" />
|
||||||
|
@ -30,11 +29,6 @@
|
||||||
<PackageReference Include="NetCoreServer" />
|
<PackageReference Include="NetCoreServer" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- Due to Concentus. -->
|
|
||||||
<PropertyGroup>
|
|
||||||
<NoWarn>NU1605</NoWarn>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Remove="Homebrew.npdm" />
|
<None Remove="Homebrew.npdm" />
|
||||||
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
<None Remove="HOS\Applets\SoftwareKeyboard\Resources\Logo_Ryujinx.png" />
|
||||||
|
|
|
@ -117,12 +117,12 @@ namespace Ryujinx.HLE
|
||||||
|
|
||||||
public void SetVolume(float volume)
|
public void SetVolume(float volume)
|
||||||
{
|
{
|
||||||
System.SetVolume(Math.Clamp(volume, 0, 1));
|
AudioDeviceDriver.Volume = Math.Clamp(volume, 0f, 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float GetVolume()
|
public float GetVolume()
|
||||||
{
|
{
|
||||||
return System.GetVolume();
|
return AudioDeviceDriver.Volume;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EnableCheats()
|
public void EnableCheats()
|
||||||
|
@ -132,7 +132,7 @@ namespace Ryujinx.HLE
|
||||||
|
|
||||||
public bool IsAudioMuted()
|
public bool IsAudioMuted()
|
||||||
{
|
{
|
||||||
return System.GetVolume() == 0;
|
return AudioDeviceDriver.Volume == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DisposeGpu()
|
public void DisposeGpu()
|
||||||
|
|
10
src/Ryujinx.Horizon.Common/IExternalEvent.cs
Normal file
10
src/Ryujinx.Horizon.Common/IExternalEvent.cs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Common
|
||||||
|
{
|
||||||
|
public interface IExternalEvent
|
||||||
|
{
|
||||||
|
void Signal();
|
||||||
|
void Clear();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,3 +1,4 @@
|
||||||
|
using Ryujinx.Memory;
|
||||||
using System;
|
using System;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Common
|
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 CreatePort(out int serverPortHandle, out int clientPortHandle, int maxSessions, bool isLight, string name);
|
||||||
Result ManageNamedPort(out int handle, string name, int maxSessions);
|
Result ManageNamedPort(out int handle, string name, int maxSessions);
|
||||||
Result ConnectToPort(out int clientSessionHandle, int clientPortHandle);
|
Result ConnectToPort(out int clientSessionHandle, int clientPortHandle);
|
||||||
|
|
||||||
|
IExternalEvent GetExternalEvent(int handle);
|
||||||
|
IVirtualMemoryManager GetMemoryManagerByProcessHandle(int handle);
|
||||||
|
ulong GetTransferMemoryAddress(int handle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,11 @@ namespace Ryujinx.Horizon.Common
|
||||||
ErrorCode = module | (description << ModuleBits);
|
ErrorCode = module | (description << ModuleBits);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Result(int errorCode)
|
||||||
|
{
|
||||||
|
ErrorCode = errorCode;
|
||||||
|
}
|
||||||
|
|
||||||
public readonly override bool Equals(object obj)
|
public readonly override bool Equals(object obj)
|
||||||
{
|
{
|
||||||
return obj is Result result && result.Equals(this);
|
return obj is Result result && result.Equals(this);
|
||||||
|
|
|
@ -286,13 +286,13 @@ namespace Ryujinx.Horizon.Generators.Hipc
|
||||||
{
|
{
|
||||||
if (IsNonSpanOutBuffer(compilation, parameter))
|
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")}";
|
argName = $"out {GenerateSpanCastElement0(canonicalTypeName, $"{argName}.Memory.Span")}";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outParameters.Add(new OutParameter(argName, canonicalTypeName, index, argType));
|
outParameters.Add(new OutParameter(argName, canonicalTypeName, outArgIndex++, argType));
|
||||||
|
|
||||||
argName = $"out {canonicalTypeName} {argName}";
|
argName = $"out {canonicalTypeName} {argName}";
|
||||||
}
|
}
|
||||||
|
|
|
@ -56,6 +56,7 @@ namespace Ryujinx.Horizon.Arp
|
||||||
{
|
{
|
||||||
_applicationInstanceManager.Dispose();
|
_applicationInstanceManager.Dispose();
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
17
src/Ryujinx.Horizon/Audio/AudioMain.cs
Normal file
17
src/Ryujinx.Horizon/Audio/AudioMain.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace Ryujinx.Horizon.Audio
|
||||||
|
{
|
||||||
|
class AudioMain : IService
|
||||||
|
{
|
||||||
|
public static void Main(ServiceTable serviceTable)
|
||||||
|
{
|
||||||
|
AudioUserIpcServer ipcServer = new();
|
||||||
|
|
||||||
|
ipcServer.Initialize();
|
||||||
|
|
||||||
|
serviceTable.SignalServiceReady();
|
||||||
|
|
||||||
|
ipcServer.ServiceRequests();
|
||||||
|
ipcServer.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
78
src/Ryujinx.Horizon/Audio/AudioManagers.cs
Normal file
78
src/Ryujinx.Horizon/Audio/AudioManagers.cs
Normal file
|
@ -0,0 +1,78 @@
|
||||||
|
using Ryujinx.Audio;
|
||||||
|
using Ryujinx.Audio.Input;
|
||||||
|
using Ryujinx.Audio.Integration;
|
||||||
|
using Ryujinx.Audio.Output;
|
||||||
|
using Ryujinx.Audio.Renderer.Device;
|
||||||
|
using Ryujinx.Audio.Renderer.Server;
|
||||||
|
using Ryujinx.Cpu;
|
||||||
|
using Ryujinx.Horizon.Sdk.Audio;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Audio
|
||||||
|
{
|
||||||
|
class AudioManagers : IDisposable
|
||||||
|
{
|
||||||
|
public AudioManager AudioManager { get; }
|
||||||
|
public AudioOutputManager AudioOutputManager { get; }
|
||||||
|
public AudioInputManager AudioInputManager { get; }
|
||||||
|
public AudioRendererManager AudioRendererManager { get; }
|
||||||
|
public VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; }
|
||||||
|
|
||||||
|
public AudioManagers(IHardwareDeviceDriver audioDeviceDriver, ITickSource tickSource)
|
||||||
|
{
|
||||||
|
AudioManager = new AudioManager();
|
||||||
|
AudioOutputManager = new AudioOutputManager();
|
||||||
|
AudioInputManager = new AudioInputManager();
|
||||||
|
AudioRendererManager = new AudioRendererManager(tickSource);
|
||||||
|
AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(audioDeviceDriver);
|
||||||
|
|
||||||
|
IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax];
|
||||||
|
|
||||||
|
for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++)
|
||||||
|
{
|
||||||
|
audioOutputRegisterBufferEvents[i] = new AudioEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioOutputManager.Initialize(audioDeviceDriver, audioOutputRegisterBufferEvents);
|
||||||
|
|
||||||
|
IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
|
||||||
|
|
||||||
|
for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++)
|
||||||
|
{
|
||||||
|
audioInputRegisterBufferEvents[i] = new AudioEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioInputManager.Initialize(audioDeviceDriver, audioInputRegisterBufferEvents);
|
||||||
|
|
||||||
|
IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax];
|
||||||
|
|
||||||
|
for (int i = 0; i < systemEvents.Length; i++)
|
||||||
|
{
|
||||||
|
systemEvents[i] = new AudioEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioManager.Initialize(audioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update);
|
||||||
|
|
||||||
|
AudioRendererManager.Initialize(systemEvents, audioDeviceDriver);
|
||||||
|
|
||||||
|
AudioManager.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
AudioManager.Dispose();
|
||||||
|
AudioOutputManager.Dispose();
|
||||||
|
AudioInputManager.Dispose();
|
||||||
|
AudioRendererManager.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
55
src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs
Normal file
55
src/Ryujinx.Horizon/Audio/AudioUserIpcServer.cs
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
using Ryujinx.Horizon.Sdk.Audio.Detail;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sm;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Audio
|
||||||
|
{
|
||||||
|
class AudioUserIpcServer
|
||||||
|
{
|
||||||
|
private const int MaxSessionsCount = 30;
|
||||||
|
|
||||||
|
private const int PointerBufferSize = 0xB40;
|
||||||
|
private const int MaxDomains = 0;
|
||||||
|
private const int MaxDomainObjects = 0;
|
||||||
|
private const int MaxPortsCount = 1;
|
||||||
|
|
||||||
|
private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
|
||||||
|
|
||||||
|
private SmApi _sm;
|
||||||
|
private ServerManager _serverManager;
|
||||||
|
private AudioManagers _managers;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
HeapAllocator allocator = new();
|
||||||
|
|
||||||
|
_sm = new SmApi();
|
||||||
|
_sm.Initialize().AbortOnFailure();
|
||||||
|
|
||||||
|
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount);
|
||||||
|
_managers = new AudioManagers(HorizonStatic.Options.AudioDeviceDriver, HorizonStatic.Options.TickSource);
|
||||||
|
|
||||||
|
AudioRendererManager audioRendererManager = new(_managers.AudioRendererManager, _managers.AudioDeviceSessionRegistry);
|
||||||
|
AudioOutManager audioOutManager = new(_managers.AudioOutputManager);
|
||||||
|
AudioInManager audioInManager = new(_managers.AudioInputManager);
|
||||||
|
FinalOutputRecorderManager finalOutputRecorderManager = new();
|
||||||
|
|
||||||
|
_serverManager.RegisterObjectForServer(audioRendererManager, ServiceName.Encode("audren:u"), MaxSessionsCount);
|
||||||
|
_serverManager.RegisterObjectForServer(audioOutManager, ServiceName.Encode("audout:u"), MaxSessionsCount);
|
||||||
|
_serverManager.RegisterObjectForServer(audioInManager, ServiceName.Encode("audin:u"), MaxSessionsCount);
|
||||||
|
_serverManager.RegisterObjectForServer(finalOutputRecorderManager, ServiceName.Encode("audrec:u"), MaxSessionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ServiceRequests()
|
||||||
|
{
|
||||||
|
_serverManager.ServiceRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Shutdown()
|
||||||
|
{
|
||||||
|
_serverManager.Dispose();
|
||||||
|
_managers.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs
Normal file
46
src/Ryujinx.Horizon/Audio/HwopusIpcServer.cs
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
using Ryujinx.Horizon.Sdk.Codec.Detail;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sm;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Audio
|
||||||
|
{
|
||||||
|
class HwopusIpcServer
|
||||||
|
{
|
||||||
|
private const int MaxSessionsCount = 24;
|
||||||
|
|
||||||
|
private const int PointerBufferSize = 0x1000;
|
||||||
|
private const int MaxDomains = 8;
|
||||||
|
private const int MaxDomainObjects = 256;
|
||||||
|
private const int MaxPortsCount = 1;
|
||||||
|
|
||||||
|
private static readonly ManagerOptions _options = new(PointerBufferSize, MaxDomains, MaxDomainObjects, false);
|
||||||
|
|
||||||
|
private SmApi _sm;
|
||||||
|
private ServerManager _serverManager;
|
||||||
|
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
HeapAllocator allocator = new();
|
||||||
|
|
||||||
|
_sm = new SmApi();
|
||||||
|
_sm.Initialize().AbortOnFailure();
|
||||||
|
|
||||||
|
_serverManager = new ServerManager(allocator, _sm, MaxPortsCount, _options, MaxSessionsCount);
|
||||||
|
|
||||||
|
HardwareOpusDecoderManager hardwareOpusDecoderManager = new();
|
||||||
|
|
||||||
|
_serverManager.RegisterObjectForServer(hardwareOpusDecoderManager, ServiceName.Encode("hwopus"), MaxSessionsCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ServiceRequests()
|
||||||
|
{
|
||||||
|
_serverManager.ServiceRequests();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Shutdown()
|
||||||
|
{
|
||||||
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
src/Ryujinx.Horizon/Audio/HwopusMain.cs
Normal file
17
src/Ryujinx.Horizon/Audio/HwopusMain.cs
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
namespace Ryujinx.Horizon.Audio
|
||||||
|
{
|
||||||
|
class HwopusMain : IService
|
||||||
|
{
|
||||||
|
public static void Main(ServiceTable serviceTable)
|
||||||
|
{
|
||||||
|
HwopusIpcServer ipcServer = new();
|
||||||
|
|
||||||
|
ipcServer.Initialize();
|
||||||
|
|
||||||
|
serviceTable.SignalServiceReady();
|
||||||
|
|
||||||
|
ipcServer.ServiceRequests();
|
||||||
|
ipcServer.Shutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -44,6 +44,7 @@ namespace Ryujinx.Horizon.Bcat
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ namespace Ryujinx.Horizon.Friends
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
using LibHac;
|
using LibHac;
|
||||||
|
using Ryujinx.Audio.Integration;
|
||||||
|
using Ryujinx.Cpu;
|
||||||
using Ryujinx.Horizon.Sdk.Account;
|
using Ryujinx.Horizon.Sdk.Account;
|
||||||
using Ryujinx.Horizon.Sdk.Fs;
|
using Ryujinx.Horizon.Sdk.Fs;
|
||||||
|
|
||||||
|
@ -12,14 +14,24 @@ namespace Ryujinx.Horizon
|
||||||
public HorizonClient BcatClient { get; }
|
public HorizonClient BcatClient { get; }
|
||||||
public IFsClient FsClient { get; }
|
public IFsClient FsClient { get; }
|
||||||
public IEmulatorAccountManager AccountManager { 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;
|
IgnoreMissingServices = ignoreMissingServices;
|
||||||
ThrowOnInvalidCommandIds = true;
|
ThrowOnInvalidCommandIds = true;
|
||||||
BcatClient = bcatClient;
|
BcatClient = bcatClient;
|
||||||
FsClient = fsClient;
|
FsClient = fsClient;
|
||||||
AccountManager = accountManager;
|
AccountManager = accountManager;
|
||||||
|
AudioDeviceDriver = audioDeviceDriver;
|
||||||
|
TickSource = tickSource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ namespace Ryujinx.Horizon.Hshl
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,6 +42,7 @@ namespace Ryujinx.Horizon.Ins
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.Lbl
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.LogManager
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,7 @@ namespace Ryujinx.Horizon.MmNv
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,6 @@ using Ryujinx.Horizon.Sdk.Fs;
|
||||||
using Ryujinx.Horizon.Sdk.Ngc.Detail;
|
using Ryujinx.Horizon.Sdk.Ngc.Detail;
|
||||||
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
using Ryujinx.Horizon.Sdk.Sm;
|
using Ryujinx.Horizon.Sdk.Sm;
|
||||||
using System;
|
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Ngc
|
namespace Ryujinx.Horizon.Ngc
|
||||||
{
|
{
|
||||||
|
@ -46,6 +45,7 @@ namespace Ryujinx.Horizon.Ngc
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
_profanityFilter.Dispose();
|
_profanityFilter.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,6 +43,7 @@ namespace Ryujinx.Horizon.Ovln
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,7 @@ namespace Ryujinx.Horizon.Prepo
|
||||||
{
|
{
|
||||||
_arp.Dispose();
|
_arp.Dispose();
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ namespace Ryujinx.Horizon.Psc
|
||||||
public void Shutdown()
|
public void Shutdown()
|
||||||
{
|
{
|
||||||
_serverManager.Dispose();
|
_serverManager.Dispose();
|
||||||
|
_sm.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" />
|
<ProjectReference Include="..\Ryujinx.Horizon.Common\Ryujinx.Horizon.Common.csproj" />
|
||||||
<ProjectReference Include="..\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
<ProjectReference Include="..\Ryujinx.Horizon.Generators\Ryujinx.Horizon.Generators.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
|
||||||
|
@ -12,7 +13,13 @@
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="Concentus" />
|
||||||
<PackageReference Include="LibHac" />
|
<PackageReference Include="LibHac" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<!-- Due to Concentus. -->
|
||||||
|
<PropertyGroup>
|
||||||
|
<NoWarn>NU1605</NoWarn>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -5,7 +5,7 @@ using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.Horizon.Sdk.Account
|
namespace Ryujinx.Horizon.Sdk.Account
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x8)]
|
||||||
public readonly record struct Uid
|
public readonly record struct Uid
|
||||||
{
|
{
|
||||||
public readonly ulong High;
|
public readonly ulong High;
|
||||||
|
|
71
src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs
Normal file
71
src/Ryujinx.Horizon/Sdk/Applet/AppletId.cs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Applet
|
||||||
|
{
|
||||||
|
enum AppletId : uint
|
||||||
|
{
|
||||||
|
None = 0x00,
|
||||||
|
Application = 0x01,
|
||||||
|
OverlayApplet = 0x02,
|
||||||
|
SystemAppletMenu = 0x03,
|
||||||
|
SystemApplication = 0x04,
|
||||||
|
LibraryAppletAuth = 0x0A,
|
||||||
|
LibraryAppletCabinet = 0x0B,
|
||||||
|
LibraryAppletController = 0x0C,
|
||||||
|
LibraryAppletDataErase = 0x0D,
|
||||||
|
LibraryAppletError = 0x0E,
|
||||||
|
LibraryAppletNetConnect = 0x0F,
|
||||||
|
LibraryAppletPlayerSelect = 0x10,
|
||||||
|
LibraryAppletSwkbd = 0x11,
|
||||||
|
LibraryAppletMiiEdit = 0x12,
|
||||||
|
LibraryAppletWeb = 0x13,
|
||||||
|
LibraryAppletShop = 0x14,
|
||||||
|
LibraryAppletPhotoViewer = 0x15,
|
||||||
|
LibraryAppletSet = 0x16,
|
||||||
|
LibraryAppletOfflineWeb = 0x17,
|
||||||
|
LibraryAppletLoginShare = 0x18,
|
||||||
|
LibraryAppletWifiWebAuth = 0x19,
|
||||||
|
LibraryAppletMyPage = 0x1A,
|
||||||
|
LibraryAppletGift = 0x1B,
|
||||||
|
LibraryAppletUserMigration = 0x1C,
|
||||||
|
LibraryAppletPreomiaSys = 0x1D,
|
||||||
|
LibraryAppletStory = 0x1E,
|
||||||
|
LibraryAppletPreomiaUsr = 0x1F,
|
||||||
|
LibraryAppletPreomiaUsrDummy = 0x20,
|
||||||
|
LibraryAppletSample = 0x21,
|
||||||
|
LibraryAppletPromoteQualification = 0x22,
|
||||||
|
LibraryAppletOfflineWebFw17 = 0x32,
|
||||||
|
LibraryAppletOfflineWeb2Fw17 = 0x33,
|
||||||
|
LibraryAppletLoginShareFw17 = 0x35,
|
||||||
|
LibraryAppletLoginShare2Fw17 = 0x36,
|
||||||
|
LibraryAppletLoginShare3Fw17 = 0x37,
|
||||||
|
Unknown38 = 0x38,
|
||||||
|
DevlopmentTool = 0x3E8,
|
||||||
|
CombinationLA = 0x3F1,
|
||||||
|
AeSystemApplet = 0x3F2,
|
||||||
|
AeOverlayApplet = 0x3F3,
|
||||||
|
AeStarter = 0x3F4,
|
||||||
|
AeLibraryAppletAlone = 0x3F5,
|
||||||
|
AeLibraryApplet1 = 0x3F6,
|
||||||
|
AeLibraryApplet2 = 0x3F7,
|
||||||
|
AeLibraryApplet3 = 0x3F8,
|
||||||
|
AeLibraryApplet4 = 0x3F9,
|
||||||
|
AppletISA = 0x3FA,
|
||||||
|
AppletIOA = 0x3FB,
|
||||||
|
AppletISTA = 0x3FC,
|
||||||
|
AppletILA1 = 0x3FD,
|
||||||
|
AppletILA2 = 0x3FE,
|
||||||
|
CombinationLAFw17 = 0x700000DC,
|
||||||
|
AeSystemAppletFw17 = 0x700000E6,
|
||||||
|
AeOverlayAppletFw17 = 0x700000E7,
|
||||||
|
AeStarterFw17 = 0x700000E8,
|
||||||
|
AeLibraryAppletAloneFw17 = 0x700000E9,
|
||||||
|
AeLibraryApplet1Fw17 = 0x700000EA,
|
||||||
|
AeLibraryApplet2Fw17 = 0x700000EB,
|
||||||
|
AeLibraryApplet3Fw17 = 0x700000EC,
|
||||||
|
AeLibraryApplet4Fw17 = 0x700000ED,
|
||||||
|
AppletISAFw17 = 0x700000F0,
|
||||||
|
AppletIOAFw17 = 0x700000F1,
|
||||||
|
AppletISTAFw17 = 0x700000F2,
|
||||||
|
AppletILA1Fw17 = 0x700000F3,
|
||||||
|
AppletILA2Fw17 = 0x700000F4,
|
||||||
|
}
|
||||||
|
}
|
15
src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs
Normal file
15
src/Ryujinx.Horizon/Sdk/Applet/AppletResourceUserId.cs
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Applet
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x8)]
|
||||||
|
readonly struct AppletResourceUserId
|
||||||
|
{
|
||||||
|
public readonly ulong Id;
|
||||||
|
|
||||||
|
public AppletResourceUserId(ulong id)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs
Normal file
50
src/Ryujinx.Horizon/Sdk/Audio/AudioEvent.cs
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
using Ryujinx.Audio.Integration;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.OsTypes;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio
|
||||||
|
{
|
||||||
|
class AudioEvent : IWritableEvent, IDisposable
|
||||||
|
{
|
||||||
|
private SystemEventType _systemEvent;
|
||||||
|
private readonly IExternalEvent _externalEvent;
|
||||||
|
|
||||||
|
public AudioEvent()
|
||||||
|
{
|
||||||
|
Os.CreateSystemEvent(out _systemEvent, EventClearMode.ManualClear, interProcess: true);
|
||||||
|
|
||||||
|
// We need to do this because the event will be signalled from a different thread.
|
||||||
|
_externalEvent = HorizonStatic.Syscall.GetExternalEvent(Os.GetWritableHandleOfSystemEvent(ref _systemEvent));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Signal()
|
||||||
|
{
|
||||||
|
_externalEvent.Signal();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_externalEvent.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public int GetReadableHandle()
|
||||||
|
{
|
||||||
|
return Os.GetReadableHandleOfSystemEvent(ref _systemEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Os.DestroySystemEvent(ref _systemEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
Normal file
12
src/Ryujinx.Horizon/Sdk/Audio/AudioResult.cs
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio
|
||||||
|
{
|
||||||
|
static class AudioResult
|
||||||
|
{
|
||||||
|
private const int ModuleId = 153;
|
||||||
|
|
||||||
|
public static Result DeviceNotFound => new(ModuleId, 1);
|
||||||
|
public static Result UnsupportedRevision => new(ModuleId, 2);
|
||||||
|
}
|
||||||
|
}
|
252
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
Normal file
252
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioDevice.cs
Normal file
|
@ -0,0 +1,252 @@
|
||||||
|
using Ryujinx.Audio.Renderer.Device;
|
||||||
|
using Ryujinx.Audio.Renderer.Server;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.Applet;
|
||||||
|
using Ryujinx.Horizon.Sdk.OsTypes;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
partial class AudioDevice : IAudioDevice, IDisposable
|
||||||
|
{
|
||||||
|
private readonly VirtualDeviceSessionRegistry _registry;
|
||||||
|
private readonly VirtualDeviceSession[] _sessions;
|
||||||
|
private readonly bool _isUsbDeviceSupported;
|
||||||
|
|
||||||
|
private SystemEventType _audioEvent;
|
||||||
|
private SystemEventType _audioInputEvent;
|
||||||
|
private SystemEventType _audioOutputEvent;
|
||||||
|
|
||||||
|
public AudioDevice(VirtualDeviceSessionRegistry registry, AppletResourceUserId appletResourceId, uint revision)
|
||||||
|
{
|
||||||
|
_registry = registry;
|
||||||
|
|
||||||
|
BehaviourContext behaviourContext = new();
|
||||||
|
behaviourContext.SetUserRevision((int)revision);
|
||||||
|
|
||||||
|
_isUsbDeviceSupported = behaviourContext.IsAudioUsbDeviceOutputSupported();
|
||||||
|
_sessions = registry.GetSessionByAppletResourceId(appletResourceId.Id);
|
||||||
|
|
||||||
|
Os.CreateSystemEvent(out _audioEvent, EventClearMode.AutoClear, interProcess: true);
|
||||||
|
Os.CreateSystemEvent(out _audioInputEvent, EventClearMode.AutoClear, interProcess: true);
|
||||||
|
Os.CreateSystemEvent(out _audioOutputEvent, EventClearMode.AutoClear, interProcess: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetDeviceByName(out VirtualDeviceSession result, string name, bool ignoreRevLimitation = false)
|
||||||
|
{
|
||||||
|
result = null;
|
||||||
|
|
||||||
|
foreach (VirtualDeviceSession session in _sessions)
|
||||||
|
{
|
||||||
|
if (session.Device.Name.Equals(name))
|
||||||
|
{
|
||||||
|
if (!ignoreRevLimitation && !_isUsbDeviceSupported && session.Device.IsUsbDevice())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
result = session;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(0)]
|
||||||
|
public Result ListAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names, out int nameCount)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
foreach (VirtualDeviceSession session in _sessions)
|
||||||
|
{
|
||||||
|
if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count >= names.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
names[count] = new DeviceName(session.Device.Name);
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameCount = count;
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(1)]
|
||||||
|
public Result SetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, float volume)
|
||||||
|
{
|
||||||
|
if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString(), ignoreRevLimitation: true))
|
||||||
|
{
|
||||||
|
if (!_isUsbDeviceSupported && result.Device.IsUsbDevice())
|
||||||
|
{
|
||||||
|
result = _sessions[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
result.Volume = volume;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(2)]
|
||||||
|
public Result GetAudioDeviceOutputVolume([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name, out float volume)
|
||||||
|
{
|
||||||
|
if (name.Length > 0 && TryGetDeviceByName(out VirtualDeviceSession result, name[0].ToString()))
|
||||||
|
{
|
||||||
|
volume = result.Volume;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
volume = 0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(3)]
|
||||||
|
public Result GetActiveAudioDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> name)
|
||||||
|
{
|
||||||
|
VirtualDevice device = _registry.ActiveDevice;
|
||||||
|
|
||||||
|
if (!_isUsbDeviceSupported && device.IsUsbDevice())
|
||||||
|
{
|
||||||
|
device = _registry.DefaultDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name.Length > 0)
|
||||||
|
{
|
||||||
|
name[0] = new DeviceName(device.Name);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(4)]
|
||||||
|
public Result QueryAudioDeviceSystemEvent([CopyHandle] out int eventHandle)
|
||||||
|
{
|
||||||
|
eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioEvent);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(5)]
|
||||||
|
public Result GetActiveChannelCount(out int channelCount)
|
||||||
|
{
|
||||||
|
VirtualDevice device = _registry.ActiveDevice;
|
||||||
|
|
||||||
|
if (!_isUsbDeviceSupported && device.IsUsbDevice())
|
||||||
|
{
|
||||||
|
device = _registry.DefaultDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
channelCount = (int)device.ChannelCount;
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(6)] // 3.0.0+
|
||||||
|
public Result ListAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names, out int nameCount)
|
||||||
|
{
|
||||||
|
return ListAudioDeviceName(names, out nameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(7)] // 3.0.0+
|
||||||
|
public Result SetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, float volume)
|
||||||
|
{
|
||||||
|
return SetAudioDeviceOutputVolume(name, volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(8)] // 3.0.0+
|
||||||
|
public Result GetAudioDeviceOutputVolumeAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name, out float volume)
|
||||||
|
{
|
||||||
|
return GetAudioDeviceOutputVolume(name, out volume);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(10)] // 3.0.0+
|
||||||
|
public Result GetActiveAudioDeviceNameAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> name)
|
||||||
|
{
|
||||||
|
return GetActiveAudioDeviceName(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(11)] // 3.0.0+
|
||||||
|
public Result QueryAudioDeviceInputEvent([CopyHandle] out int eventHandle)
|
||||||
|
{
|
||||||
|
eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioInputEvent);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(12)] // 3.0.0+
|
||||||
|
public Result QueryAudioDeviceOutputEvent([CopyHandle] out int eventHandle)
|
||||||
|
{
|
||||||
|
eventHandle = Os.GetReadableHandleOfSystemEvent(ref _audioOutputEvent);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(13)] // 13.0.0+
|
||||||
|
public Result GetActiveAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> name)
|
||||||
|
{
|
||||||
|
if (name.Length > 0)
|
||||||
|
{
|
||||||
|
name[0] = new DeviceName(_registry.ActiveDevice.GetOutputDeviceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(14)] // 13.0.0+
|
||||||
|
public Result ListAudioOutputDeviceName([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names, out int nameCount)
|
||||||
|
{
|
||||||
|
int count = 0;
|
||||||
|
|
||||||
|
foreach (VirtualDeviceSession session in _sessions)
|
||||||
|
{
|
||||||
|
if (!_isUsbDeviceSupported && session.Device.IsUsbDevice())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count >= names.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
names[count] = new DeviceName(session.Device.GetOutputDeviceName());
|
||||||
|
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
nameCount = count;
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
Os.DestroySystemEvent(ref _audioEvent);
|
||||||
|
Os.DestroySystemEvent(ref _audioInputEvent);
|
||||||
|
Os.DestroySystemEvent(ref _audioOutputEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
171
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs
Normal file
171
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioIn.cs
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
using Ryujinx.Audio.Common;
|
||||||
|
using Ryujinx.Audio.Input;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
partial class AudioIn : IAudioIn, IDisposable
|
||||||
|
{
|
||||||
|
private readonly AudioInputSystem _impl;
|
||||||
|
private int _processHandle;
|
||||||
|
|
||||||
|
public AudioIn(AudioInputSystem impl, int processHandle)
|
||||||
|
{
|
||||||
|
_impl = impl;
|
||||||
|
_processHandle = processHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(0)]
|
||||||
|
public Result GetAudioInState(out AudioDeviceState state)
|
||||||
|
{
|
||||||
|
state = _impl.GetState();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(1)]
|
||||||
|
public Result Start()
|
||||||
|
{
|
||||||
|
return new Result((int)_impl.Start());
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(2)]
|
||||||
|
public Result Stop()
|
||||||
|
{
|
||||||
|
return new Result((int)_impl.Stop());
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(3)]
|
||||||
|
public Result AppendAudioInBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer)
|
||||||
|
{
|
||||||
|
AudioUserBuffer userBuffer = default;
|
||||||
|
|
||||||
|
if (buffer.Length > 0)
|
||||||
|
{
|
||||||
|
userBuffer = buffer[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(4)]
|
||||||
|
public Result RegisterBufferEvent([CopyHandle] out int eventHandle)
|
||||||
|
{
|
||||||
|
eventHandle = 0;
|
||||||
|
|
||||||
|
if (_impl.RegisterBufferEvent() is AudioEvent audioEvent)
|
||||||
|
{
|
||||||
|
eventHandle = audioEvent.GetReadableHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(5)]
|
||||||
|
public Result GetReleasedAudioInBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ulong> bufferTags)
|
||||||
|
{
|
||||||
|
return new Result((int)_impl.GetReleasedBuffers(bufferTags, out count));
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(6)]
|
||||||
|
public Result ContainsAudioInBuffer(out bool contains, ulong bufferTag)
|
||||||
|
{
|
||||||
|
contains = _impl.ContainsBuffer(bufferTag);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(7)] // 3.0.0+
|
||||||
|
public Result AppendUacInBuffer(
|
||||||
|
ulong bufferTag,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer,
|
||||||
|
[CopyHandle] int eventHandle)
|
||||||
|
{
|
||||||
|
AudioUserBuffer userBuffer = default;
|
||||||
|
|
||||||
|
if (buffer.Length > 0)
|
||||||
|
{
|
||||||
|
userBuffer = buffer[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Result((int)_impl.AppendUacBuffer(bufferTag, ref userBuffer, (uint)eventHandle));
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(8)] // 3.0.0+
|
||||||
|
public Result AppendAudioInBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer)
|
||||||
|
{
|
||||||
|
return AppendAudioInBuffer(bufferTag, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(9)] // 3.0.0+
|
||||||
|
public Result GetReleasedAudioInBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<ulong> bufferTags)
|
||||||
|
{
|
||||||
|
return GetReleasedAudioInBuffers(out count, bufferTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(10)] // 3.0.0+
|
||||||
|
public Result AppendUacInBufferAuto(
|
||||||
|
ulong bufferTag,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer,
|
||||||
|
[CopyHandle] int eventHandle)
|
||||||
|
{
|
||||||
|
return AppendUacInBuffer(bufferTag, buffer, eventHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(11)] // 4.0.0+
|
||||||
|
public Result GetAudioInBufferCount(out uint bufferCount)
|
||||||
|
{
|
||||||
|
bufferCount = _impl.GetBufferCount();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(12)] // 4.0.0+
|
||||||
|
public Result SetDeviceGain(float gain)
|
||||||
|
{
|
||||||
|
_impl.SetVolume(gain);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(13)] // 4.0.0+
|
||||||
|
public Result GetDeviceGain(out float gain)
|
||||||
|
{
|
||||||
|
gain = _impl.GetVolume();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(14)] // 6.0.0+
|
||||||
|
public Result FlushAudioInBuffers(out bool pending)
|
||||||
|
{
|
||||||
|
pending = _impl.FlushBuffers();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_impl.Dispose();
|
||||||
|
|
||||||
|
if (_processHandle != 0)
|
||||||
|
{
|
||||||
|
HorizonStatic.Syscall.CloseHandle(_processHandle);
|
||||||
|
|
||||||
|
_processHandle = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
130
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs
Normal file
130
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInManager.cs
Normal file
|
@ -0,0 +1,130 @@
|
||||||
|
using Ryujinx.Audio;
|
||||||
|
using Ryujinx.Audio.Common;
|
||||||
|
using Ryujinx.Audio.Input;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.Applet;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
partial class AudioInManager : IAudioInManager
|
||||||
|
{
|
||||||
|
private readonly AudioInputManager _impl;
|
||||||
|
|
||||||
|
public AudioInManager(AudioInputManager impl)
|
||||||
|
{
|
||||||
|
_impl = impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(0)]
|
||||||
|
public Result ListAudioIns(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names)
|
||||||
|
{
|
||||||
|
string[] deviceNames = _impl.ListAudioIns(filtered: false);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
foreach (string deviceName in deviceNames)
|
||||||
|
{
|
||||||
|
if (count >= names.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
names[count++] = new DeviceName(deviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(1)]
|
||||||
|
public Result OpenAudioIn(
|
||||||
|
out AudioOutputConfiguration outputConfiguration,
|
||||||
|
out IAudioIn audioIn,
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
|
||||||
|
AudioInputConfiguration parameter,
|
||||||
|
AppletResourceUserId appletResourceId,
|
||||||
|
[CopyHandle] int processHandle,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
|
||||||
|
[ClientProcessId] ulong pid)
|
||||||
|
{
|
||||||
|
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
||||||
|
|
||||||
|
ResultCode rc = _impl.OpenAudioIn(
|
||||||
|
out string outputDeviceName,
|
||||||
|
out outputConfiguration,
|
||||||
|
out AudioInputSystem inSystem,
|
||||||
|
clientMemoryManager,
|
||||||
|
name.Length > 0 ? name[0].ToString() : string.Empty,
|
||||||
|
SampleFormat.PcmInt16,
|
||||||
|
ref parameter);
|
||||||
|
|
||||||
|
if (rc == ResultCode.Success && outName.Length > 0)
|
||||||
|
{
|
||||||
|
outName[0] = new DeviceName(outputDeviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
audioIn = new AudioIn(inSystem, processHandle);
|
||||||
|
|
||||||
|
return new Result((int)rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(2)] // 3.0.0+
|
||||||
|
public Result ListAudioInsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
|
||||||
|
{
|
||||||
|
return ListAudioIns(out count, names);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(3)] // 3.0.0+
|
||||||
|
public Result OpenAudioInAuto(
|
||||||
|
out AudioOutputConfiguration outputConfig,
|
||||||
|
out IAudioIn audioIn,
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> outName,
|
||||||
|
AudioInputConfiguration parameter,
|
||||||
|
AppletResourceUserId appletResourceId,
|
||||||
|
[CopyHandle] int processHandle,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name,
|
||||||
|
[ClientProcessId] ulong pid)
|
||||||
|
{
|
||||||
|
return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(4)] // 3.0.0+
|
||||||
|
public Result ListAudioInsAutoFiltered(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
|
||||||
|
{
|
||||||
|
string[] deviceNames = _impl.ListAudioIns(filtered: true);
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
foreach (string deviceName in deviceNames)
|
||||||
|
{
|
||||||
|
if (count >= names.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
names[count++] = new DeviceName(deviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(5)] // 5.0.0+
|
||||||
|
public Result OpenAudioInProtocolSpecified(
|
||||||
|
out AudioOutputConfiguration outputConfig,
|
||||||
|
out IAudioIn audioIn,
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
|
||||||
|
AudioInProtocol protocol,
|
||||||
|
AudioInputConfiguration parameter,
|
||||||
|
AppletResourceUserId appletResourceId,
|
||||||
|
[CopyHandle] int processHandle,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
|
||||||
|
[ClientProcessId] ulong pid)
|
||||||
|
{
|
||||||
|
// NOTE: We always assume that only the default device will be plugged (we never report any USB Audio Class type devices).
|
||||||
|
|
||||||
|
return OpenAudioIn(out outputConfig, out audioIn, outName, parameter, appletResourceId, processHandle, name, pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs
Normal file
23
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioInProtocol.cs
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x1)]
|
||||||
|
struct AudioInProtocol
|
||||||
|
{
|
||||||
|
public AudioInProtocolName Name;
|
||||||
|
public Array7<byte> Padding;
|
||||||
|
|
||||||
|
public AudioInProtocol(AudioInProtocolName name)
|
||||||
|
{
|
||||||
|
Name = name;
|
||||||
|
Padding = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override readonly string ToString()
|
||||||
|
{
|
||||||
|
return Name.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
enum AudioInProtocolName : byte
|
||||||
|
{
|
||||||
|
DeviceIn = 0,
|
||||||
|
UacIn = 1,
|
||||||
|
}
|
||||||
|
}
|
154
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs
Normal file
154
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOut.cs
Normal file
|
@ -0,0 +1,154 @@
|
||||||
|
using Ryujinx.Audio.Common;
|
||||||
|
using Ryujinx.Audio.Output;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
partial class AudioOut : IAudioOut, IDisposable
|
||||||
|
{
|
||||||
|
private readonly AudioOutputSystem _impl;
|
||||||
|
private int _processHandle;
|
||||||
|
|
||||||
|
public AudioOut(AudioOutputSystem impl, int processHandle)
|
||||||
|
{
|
||||||
|
_impl = impl;
|
||||||
|
_processHandle = processHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(0)]
|
||||||
|
public Result GetAudioOutState(out AudioDeviceState state)
|
||||||
|
{
|
||||||
|
state = _impl.GetState();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(1)]
|
||||||
|
public Result Start()
|
||||||
|
{
|
||||||
|
return new Result((int)_impl.Start());
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(2)]
|
||||||
|
public Result Stop()
|
||||||
|
{
|
||||||
|
return new Result((int)_impl.Stop());
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(3)]
|
||||||
|
public Result AppendAudioOutBuffer(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<AudioUserBuffer> buffer)
|
||||||
|
{
|
||||||
|
AudioUserBuffer userBuffer = default;
|
||||||
|
|
||||||
|
if (buffer.Length > 0)
|
||||||
|
{
|
||||||
|
userBuffer = buffer[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Result((int)_impl.AppendBuffer(bufferTag, ref userBuffer));
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(4)]
|
||||||
|
public Result RegisterBufferEvent([CopyHandle] out int eventHandle)
|
||||||
|
{
|
||||||
|
eventHandle = 0;
|
||||||
|
|
||||||
|
if (_impl.RegisterBufferEvent() is AudioEvent audioEvent)
|
||||||
|
{
|
||||||
|
eventHandle = audioEvent.GetReadableHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(5)]
|
||||||
|
public Result GetReleasedAudioOutBuffers(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<ulong> bufferTags)
|
||||||
|
{
|
||||||
|
return new Result((int)_impl.GetReleasedBuffer(bufferTags, out count));
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(6)]
|
||||||
|
public Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag)
|
||||||
|
{
|
||||||
|
contains = _impl.ContainsBuffer(bufferTag);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(7)] // 3.0.0+
|
||||||
|
public Result AppendAudioOutBufferAuto(ulong bufferTag, [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<AudioUserBuffer> buffer)
|
||||||
|
{
|
||||||
|
return AppendAudioOutBuffer(bufferTag, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(8)] // 3.0.0+
|
||||||
|
public Result GetReleasedAudioOutBuffersAuto(out uint count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<ulong> bufferTags)
|
||||||
|
{
|
||||||
|
return GetReleasedAudioOutBuffers(out count, bufferTags);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(9)] // 4.0.0+
|
||||||
|
public Result GetAudioOutBufferCount(out uint bufferCount)
|
||||||
|
{
|
||||||
|
bufferCount = _impl.GetBufferCount();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(10)] // 4.0.0+
|
||||||
|
public Result GetAudioOutPlayedSampleCount(out ulong sampleCount)
|
||||||
|
{
|
||||||
|
sampleCount = _impl.GetPlayedSampleCount();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(11)] // 4.0.0+
|
||||||
|
public Result FlushAudioOutBuffers(out bool pending)
|
||||||
|
{
|
||||||
|
pending = _impl.FlushBuffers();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(12)] // 6.0.0+
|
||||||
|
public Result SetAudioOutVolume(float volume)
|
||||||
|
{
|
||||||
|
_impl.SetVolume(volume);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(13)] // 6.0.0+
|
||||||
|
public Result GetAudioOutVolume(out float volume)
|
||||||
|
{
|
||||||
|
volume = _impl.GetVolume();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_impl.Dispose();
|
||||||
|
|
||||||
|
if (_processHandle != 0)
|
||||||
|
{
|
||||||
|
HorizonStatic.Syscall.CloseHandle(_processHandle);
|
||||||
|
|
||||||
|
_processHandle = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
93
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs
Normal file
93
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioOutManager.cs
Normal file
|
@ -0,0 +1,93 @@
|
||||||
|
using Ryujinx.Audio;
|
||||||
|
using Ryujinx.Audio.Common;
|
||||||
|
using Ryujinx.Audio.Output;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.Applet;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
partial class AudioOutManager : IAudioOutManager
|
||||||
|
{
|
||||||
|
private readonly AudioOutputManager _impl;
|
||||||
|
|
||||||
|
public AudioOutManager(AudioOutputManager impl)
|
||||||
|
{
|
||||||
|
_impl = impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(0)]
|
||||||
|
public Result ListAudioOuts(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> names)
|
||||||
|
{
|
||||||
|
string[] deviceNames = _impl.ListAudioOuts();
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
|
||||||
|
foreach (string deviceName in deviceNames)
|
||||||
|
{
|
||||||
|
if (count >= names.Length)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
names[count++] = new DeviceName(deviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(1)]
|
||||||
|
public Result OpenAudioOut(
|
||||||
|
out AudioOutputConfiguration outputConfig,
|
||||||
|
out IAudioOut audioOut,
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<DeviceName> outName,
|
||||||
|
AudioInputConfiguration parameter,
|
||||||
|
AppletResourceUserId appletResourceId,
|
||||||
|
[CopyHandle] int processHandle,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<DeviceName> name,
|
||||||
|
[ClientProcessId] ulong pid)
|
||||||
|
{
|
||||||
|
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
||||||
|
|
||||||
|
ResultCode rc = _impl.OpenAudioOut(
|
||||||
|
out string outputDeviceName,
|
||||||
|
out outputConfig,
|
||||||
|
out AudioOutputSystem outSystem,
|
||||||
|
clientMemoryManager,
|
||||||
|
name.Length > 0 ? name[0].ToString() : string.Empty,
|
||||||
|
SampleFormat.PcmInt16,
|
||||||
|
ref parameter);
|
||||||
|
|
||||||
|
if (rc == ResultCode.Success && outName.Length > 0)
|
||||||
|
{
|
||||||
|
outName[0] = new DeviceName(outputDeviceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
audioOut = new AudioOut(outSystem, processHandle);
|
||||||
|
|
||||||
|
return new Result((int)rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(2)] // 3.0.0+
|
||||||
|
public Result ListAudioOutsAuto(out int count, [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> names)
|
||||||
|
{
|
||||||
|
return ListAudioOuts(out count, names);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(3)] // 3.0.0+
|
||||||
|
public Result OpenAudioOutAuto(
|
||||||
|
out AudioOutputConfiguration outputConfig,
|
||||||
|
out IAudioOut audioOut,
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<DeviceName> outName,
|
||||||
|
AudioInputConfiguration parameter,
|
||||||
|
AppletResourceUserId appletResourceId,
|
||||||
|
[CopyHandle] int processHandle,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<DeviceName> name,
|
||||||
|
[ClientProcessId] ulong pid)
|
||||||
|
{
|
||||||
|
return OpenAudioOut(out outputConfig, out audioOut, outName, parameter, appletResourceId, processHandle, name, pid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
187
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs
Normal file
187
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs
Normal file
|
@ -0,0 +1,187 @@
|
||||||
|
using Ryujinx.Audio;
|
||||||
|
using Ryujinx.Audio.Integration;
|
||||||
|
using Ryujinx.Audio.Renderer.Server;
|
||||||
|
using Ryujinx.Common.Memory;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf.Hipc;
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
partial class AudioRenderer : IAudioRenderer, IDisposable
|
||||||
|
{
|
||||||
|
private readonly AudioRenderSystem _renderSystem;
|
||||||
|
private int _workBufferHandle;
|
||||||
|
private int _processHandle;
|
||||||
|
|
||||||
|
public AudioRenderer(AudioRenderSystem renderSystem, int workBufferHandle, int processHandle)
|
||||||
|
{
|
||||||
|
_renderSystem = renderSystem;
|
||||||
|
_workBufferHandle = workBufferHandle;
|
||||||
|
_processHandle = processHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(0)]
|
||||||
|
public Result GetSampleRate(out int sampleRate)
|
||||||
|
{
|
||||||
|
sampleRate = (int)_renderSystem.GetSampleRate();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(1)]
|
||||||
|
public Result GetSampleCount(out int sampleCount)
|
||||||
|
{
|
||||||
|
sampleCount = (int)_renderSystem.GetSampleCount();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(2)]
|
||||||
|
public Result GetMixBufferCount(out int mixBufferCount)
|
||||||
|
{
|
||||||
|
mixBufferCount = (int)_renderSystem.GetMixBufferCount();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(3)]
|
||||||
|
public Result GetState(out int state)
|
||||||
|
{
|
||||||
|
state = _renderSystem.IsActive() ? 0 : 1;
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(4)]
|
||||||
|
public Result RequestUpdate(
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output,
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> performanceOutput,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input)
|
||||||
|
{
|
||||||
|
using IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(output.Length);
|
||||||
|
using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length);
|
||||||
|
|
||||||
|
Memory<byte> outputMemory = outputOwner.Memory;
|
||||||
|
Memory<byte> performanceOutputMemory = performanceOutputOwner.Memory;
|
||||||
|
|
||||||
|
using MemoryHandle outputHandle = outputMemory.Pin();
|
||||||
|
using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin();
|
||||||
|
|
||||||
|
Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray()));
|
||||||
|
|
||||||
|
outputMemory.Span.CopyTo(output);
|
||||||
|
performanceOutputMemory.Span.CopyTo(performanceOutput);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(5)]
|
||||||
|
public Result Start()
|
||||||
|
{
|
||||||
|
_renderSystem.Start();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(6)]
|
||||||
|
public Result Stop()
|
||||||
|
{
|
||||||
|
_renderSystem.Stop();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(7)]
|
||||||
|
public Result QuerySystemEvent([CopyHandle] out int eventHandle)
|
||||||
|
{
|
||||||
|
ResultCode rc = _renderSystem.QuerySystemEvent(out IWritableEvent systemEvent);
|
||||||
|
|
||||||
|
eventHandle = 0;
|
||||||
|
|
||||||
|
if (rc == ResultCode.Success && systemEvent is AudioEvent audioEvent)
|
||||||
|
{
|
||||||
|
eventHandle = audioEvent.GetReadableHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Result((int)rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(8)]
|
||||||
|
public Result SetRenderingTimeLimit(int percent)
|
||||||
|
{
|
||||||
|
_renderSystem.SetRenderingTimeLimitPercent((uint)percent);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(9)]
|
||||||
|
public Result GetRenderingTimeLimit(out int percent)
|
||||||
|
{
|
||||||
|
percent = (int)_renderSystem.GetRenderingTimeLimit();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(10)] // 3.0.0+
|
||||||
|
public Result RequestUpdateAuto(
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> output,
|
||||||
|
[Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> performanceOutput,
|
||||||
|
[Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> input)
|
||||||
|
{
|
||||||
|
return RequestUpdate(output, performanceOutput, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(11)] // 3.0.0+
|
||||||
|
public Result ExecuteAudioRendererRendering()
|
||||||
|
{
|
||||||
|
return new Result((int)_renderSystem.ExecuteAudioRendererRendering());
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(12)] // 15.0.0+
|
||||||
|
public Result SetVoiceDropParameter(float voiceDropParameter)
|
||||||
|
{
|
||||||
|
_renderSystem.SetVoiceDropParameter(voiceDropParameter);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(13)] // 15.0.0+
|
||||||
|
public Result GetVoiceDropParameter(out float voiceDropParameter)
|
||||||
|
{
|
||||||
|
voiceDropParameter = _renderSystem.GetVoiceDropParameter();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
if (disposing)
|
||||||
|
{
|
||||||
|
_renderSystem.Dispose();
|
||||||
|
|
||||||
|
if (_workBufferHandle != 0)
|
||||||
|
{
|
||||||
|
HorizonStatic.Syscall.CloseHandle(_workBufferHandle);
|
||||||
|
|
||||||
|
_workBufferHandle = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_processHandle != 0)
|
||||||
|
{
|
||||||
|
HorizonStatic.Syscall.CloseHandle(_processHandle);
|
||||||
|
|
||||||
|
_processHandle = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(disposing: true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
132
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs
Normal file
132
src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRendererManager.cs
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
using Ryujinx.Audio.Renderer.Device;
|
||||||
|
using Ryujinx.Audio.Renderer.Server;
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
|
using Ryujinx.Horizon.Common;
|
||||||
|
using Ryujinx.Horizon.Sdk.Applet;
|
||||||
|
using Ryujinx.Horizon.Sdk.Sf;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
partial class AudioRendererManager : IAudioRendererManager
|
||||||
|
{
|
||||||
|
private const uint InitialRevision = ('R' << 0) | ('E' << 8) | ('V' << 16) | ('1' << 24);
|
||||||
|
|
||||||
|
private readonly Ryujinx.Audio.Renderer.Server.AudioRendererManager _impl;
|
||||||
|
private readonly VirtualDeviceSessionRegistry _registry;
|
||||||
|
|
||||||
|
public AudioRendererManager(Ryujinx.Audio.Renderer.Server.AudioRendererManager impl, VirtualDeviceSessionRegistry registry)
|
||||||
|
{
|
||||||
|
_impl = impl;
|
||||||
|
_registry = registry;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(0)]
|
||||||
|
public Result OpenAudioRenderer(
|
||||||
|
out IAudioRenderer renderer,
|
||||||
|
AudioRendererParameterInternal parameter,
|
||||||
|
[CopyHandle] int workBufferHandle,
|
||||||
|
[CopyHandle] int processHandle,
|
||||||
|
ulong workBufferSize,
|
||||||
|
AppletResourceUserId appletResourceId,
|
||||||
|
[ClientProcessId] ulong pid)
|
||||||
|
{
|
||||||
|
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
||||||
|
ulong workBufferAddress = HorizonStatic.Syscall.GetTransferMemoryAddress(workBufferHandle);
|
||||||
|
|
||||||
|
Result result = new Result((int)_impl.OpenAudioRenderer(
|
||||||
|
out var renderSystem,
|
||||||
|
clientMemoryManager,
|
||||||
|
ref parameter.Configuration,
|
||||||
|
appletResourceId.Id,
|
||||||
|
workBufferAddress,
|
||||||
|
workBufferSize,
|
||||||
|
(uint)processHandle));
|
||||||
|
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
renderer = new AudioRenderer(renderSystem, workBufferHandle, processHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderer = null;
|
||||||
|
|
||||||
|
HorizonStatic.Syscall.CloseHandle(workBufferHandle);
|
||||||
|
HorizonStatic.Syscall.CloseHandle(processHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(1)]
|
||||||
|
public Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter)
|
||||||
|
{
|
||||||
|
if (BehaviourContext.CheckValidRevision(parameter.Configuration.Revision))
|
||||||
|
{
|
||||||
|
workBufferSize = (long)Ryujinx.Audio.Renderer.Server.AudioRendererManager.GetWorkBufferSize(ref parameter.Configuration);
|
||||||
|
|
||||||
|
Logger.Debug?.Print(LogClass.ServiceAudio, $"WorkBufferSize is 0x{workBufferSize:x16}.");
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
workBufferSize = 0;
|
||||||
|
|
||||||
|
Logger.Warning?.Print(LogClass.ServiceAudio, $"Library Revision REV{BehaviourContext.GetRevisionNumber(parameter.Configuration.Revision)} is not supported!");
|
||||||
|
|
||||||
|
return AudioResult.UnsupportedRevision;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(2)]
|
||||||
|
public Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId)
|
||||||
|
{
|
||||||
|
audioDevice = new AudioDevice(_registry, appletResourceId, InitialRevision);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(3)] // 3.0.0+
|
||||||
|
public Result OpenAudioRendererForManualExecution(
|
||||||
|
out IAudioRenderer renderer,
|
||||||
|
AudioRendererParameterInternal parameter,
|
||||||
|
ulong workBufferAddress,
|
||||||
|
[CopyHandle] int processHandle,
|
||||||
|
ulong workBufferSize,
|
||||||
|
AppletResourceUserId appletResourceId,
|
||||||
|
[ClientProcessId] ulong pid)
|
||||||
|
{
|
||||||
|
var clientMemoryManager = HorizonStatic.Syscall.GetMemoryManagerByProcessHandle(processHandle);
|
||||||
|
|
||||||
|
Result result = new Result((int)_impl.OpenAudioRenderer(
|
||||||
|
out var renderSystem,
|
||||||
|
clientMemoryManager,
|
||||||
|
ref parameter.Configuration,
|
||||||
|
appletResourceId.Id,
|
||||||
|
workBufferAddress,
|
||||||
|
workBufferSize,
|
||||||
|
(uint)processHandle));
|
||||||
|
|
||||||
|
if (result.IsSuccess)
|
||||||
|
{
|
||||||
|
renderer = new AudioRenderer(renderSystem, 0, processHandle);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
renderer = null;
|
||||||
|
|
||||||
|
HorizonStatic.Syscall.CloseHandle(processHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[CmifCommand(4)] // 4.0.0+
|
||||||
|
public Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletResourceId, uint revision)
|
||||||
|
{
|
||||||
|
audioDevice = new AudioDevice(_registry, appletResourceId, revision);
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
using Ryujinx.Audio.Renderer.Parameter;
|
||||||
|
|
||||||
|
namespace Ryujinx.Horizon.Sdk.Audio.Detail
|
||||||
|
{
|
||||||
|
struct AudioRendererParameterInternal
|
||||||
|
{
|
||||||
|
public AudioRendererConfiguration Configuration;
|
||||||
|
|
||||||
|
public AudioRendererParameterInternal(AudioRendererConfiguration configuration)
|
||||||
|
{
|
||||||
|
Configuration = configuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue