1
1
mirror of https://github.com/ryujinx-mirror/ryujinx.git synced 2025-09-11 16:56:33 -05:00

Migrate Audio service to new IPC (#6285)

* Migrate audren to new IPC

* Migrate audout

* Migrate audin

* Migrate hwopus

* Bye bye old audio service

* Switch volume control to IHardwareDeviceDriver

* Somewhat unrelated changes

* Remove Concentus reference from HLE

* Implement OpenAudioRendererForManualExecution

* Remove SetVolume/GetVolume methods that are not necessary

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

* Fix incorrect volume update

* PR feedback

* PR feedback

* Stub audrec

* Init outParameter

* Make FinalOutputRecorderParameter/Internal readonly

* Make FinalOutputRecorder IDisposable

* Fix HardwareOpusDecoderManager parameter buffers

* Opus work buffer size and error handling improvements

* Add AudioInProtocolName enum

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,30 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class AudioSnoopManager : IAudioSnoopManager
{
// Note: The interface changed completely on firmware 17.0.0, this implementation is for older firmware.
[CmifCommand(0)]
public Result EnableDspUsageMeasurement()
{
return Result.Success;
}
[CmifCommand(1)]
public Result DisableDspUsageMeasurement()
{
return Result.Success;
}
[CmifCommand(6)]
public Result GetDspUsage(out uint usage)
{
usage = 0;
return Result.Success;
}
}
}

View File

@@ -0,0 +1,30 @@
using Ryujinx.Common.Memory;
using System;
using System.Runtime.InteropServices;
using System.Text;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x100, Pack = 1)]
struct DeviceName
{
public Array256<byte> Name;
public DeviceName(string name)
{
Name = new();
Encoding.ASCII.GetBytes(name, Name.AsSpan());
}
public override string ToString()
{
int length = Name.AsSpan().IndexOf((byte)0);
if (length < 0)
{
length = 0x100;
}
return Encoding.ASCII.GetString(Name.AsSpan()[..length]);
}
}
}

View File

@@ -0,0 +1,147 @@
using Ryujinx.Common.Logging;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.OsTypes;
using Ryujinx.Horizon.Sdk.Sf;
using Ryujinx.Horizon.Sdk.Sf.Hipc;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class FinalOutputRecorder : IFinalOutputRecorder, IDisposable
{
private int _processHandle;
private SystemEventType _event;
public FinalOutputRecorder(int processHandle)
{
_processHandle = processHandle;
Os.CreateSystemEvent(out _event, EventClearMode.ManualClear, interProcess: true);
}
[CmifCommand(0)]
public Result GetFinalOutputRecorderState(out uint state)
{
state = 0;
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return Result.Success;
}
[CmifCommand(1)]
public Result Start()
{
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return Result.Success;
}
[CmifCommand(2)]
public Result Stop()
{
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return Result.Success;
}
[CmifCommand(3)]
public Result AppendFinalOutputRecorderBuffer([Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> buffer, ulong bufferClientPtr)
{
Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferClientPtr });
return Result.Success;
}
[CmifCommand(4)]
public Result RegisterBufferEvent([CopyHandle] out int eventHandle)
{
eventHandle = Os.GetReadableHandleOfSystemEvent(ref _event);
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return Result.Success;
}
[CmifCommand(5)]
public Result GetReleasedFinalOutputRecorderBuffers([Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> buffer, out uint count, out ulong released)
{
count = 0;
released = 0;
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return Result.Success;
}
[CmifCommand(6)]
public Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains)
{
contains = false;
Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer });
return Result.Success;
}
[CmifCommand(7)]
public Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released)
{
released = 0;
Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { bufferPointer });
return Result.Success;
}
[CmifCommand(8)] // 3.0.0+
public Result AppendFinalOutputRecorderBufferAuto([Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> buffer, ulong bufferClientPtr)
{
return AppendFinalOutputRecorderBuffer(buffer, bufferClientPtr);
}
[CmifCommand(9)] // 3.0.0+
public Result GetReleasedFinalOutputRecorderBuffersAuto([Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> buffer, out uint count, out ulong released)
{
return GetReleasedFinalOutputRecorderBuffers(buffer, out count, out released);
}
[CmifCommand(10)] // 6.0.0+
public Result FlushFinalOutputRecorderBuffers(out bool pending)
{
pending = false;
Logger.Stub?.PrintStub(LogClass.ServiceAudio);
return Result.Success;
}
[CmifCommand(11)] // 9.0.0+
public Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter)
{
Logger.Stub?.PrintStub(LogClass.ServiceAudio, new { parameter });
return Result.Success;
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
Os.DestroySystemEvent(ref _event);
if (_processHandle != 0)
{
HorizonStatic.Syscall.CloseHandle(_processHandle);
_processHandle = 0;
}
}
}
public void Dispose()
{
Dispose(disposing: true);
GC.SuppressFinalize(this);
}
}
}

View File

@@ -0,0 +1,23 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
partial class FinalOutputRecorderManager : IFinalOutputRecorderManager
{
[CmifCommand(0)]
public Result OpenFinalOutputRecorder(
out IFinalOutputRecorder recorder,
FinalOutputRecorderParameter parameter,
[CopyHandle] int processHandle,
out FinalOutputRecorderParameterInternal outParameter,
AppletResourceUserId appletResourceId)
{
recorder = new FinalOutputRecorder(processHandle);
outParameter = new(parameter.SampleRate, 2, 0);
return Result.Success;
}
}
}

View File

@@ -0,0 +1,17 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x8, Pack = 0x4)]
readonly struct FinalOutputRecorderParameter
{
public readonly uint SampleRate;
public readonly uint Padding;
public FinalOutputRecorderParameter(uint sampleRate)
{
SampleRate = sampleRate;
Padding = 0;
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
[StructLayout(LayoutKind.Sequential, Size = 0x10, Pack = 0x4)]
readonly struct FinalOutputRecorderParameterInternal
{
public readonly uint SampleRate;
public readonly uint ChannelCount;
public readonly uint UseLargeFrameSize;
public readonly uint Padding;
public FinalOutputRecorderParameterInternal(uint sampleRate, uint channelCount, uint useLargeFrameSize)
{
SampleRate = sampleRate;
ChannelCount = channelCount;
UseLargeFrameSize = useLargeFrameSize;
Padding = 0;
}
}
}

View File

@@ -0,0 +1,24 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IAudioDevice : IServiceObject
{
Result ListAudioDeviceName(Span<DeviceName> names, out int nameCount);
Result SetAudioDeviceOutputVolume(ReadOnlySpan<DeviceName> name, float volume);
Result GetAudioDeviceOutputVolume(ReadOnlySpan<DeviceName> name, out float volume);
Result GetActiveAudioDeviceName(Span<DeviceName> name);
Result QueryAudioDeviceSystemEvent(out int eventHandle);
Result GetActiveChannelCount(out int channelCount);
Result ListAudioDeviceNameAuto(Span<DeviceName> names, out int nameCount);
Result SetAudioDeviceOutputVolumeAuto(ReadOnlySpan<DeviceName> name, float volume);
Result GetAudioDeviceOutputVolumeAuto(ReadOnlySpan<DeviceName> name, out float volume);
Result GetActiveAudioDeviceNameAuto(Span<DeviceName> name);
Result QueryAudioDeviceInputEvent(out int eventHandle);
Result QueryAudioDeviceOutputEvent(out int eventHandle);
Result GetActiveAudioOutputDeviceName(Span<DeviceName> name);
Result ListAudioOutputDeviceName(Span<DeviceName> names, out int nameCount);
}
}

View File

@@ -0,0 +1,26 @@
using Ryujinx.Audio.Common;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IAudioIn : IServiceObject
{
Result GetAudioInState(out AudioDeviceState state);
Result Start();
Result Stop();
Result AppendAudioInBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer);
Result RegisterBufferEvent(out int eventHandle);
Result GetReleasedAudioInBuffers(out uint count, Span<ulong> bufferTags);
Result ContainsAudioInBuffer(out bool contains, ulong bufferTag);
Result AppendUacInBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer, int eventHandle);
Result AppendAudioInBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer);
Result GetReleasedAudioInBuffersAuto(out uint count, Span<ulong> bufferTags);
Result AppendUacInBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer, int eventHandle);
Result GetAudioInBufferCount(out uint bufferCount);
Result SetDeviceGain(float gain);
Result GetDeviceGain(out float gain);
Result FlushAudioInBuffers(out bool pending);
}
}

View File

@@ -0,0 +1,43 @@
using Ryujinx.Audio.Common;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IAudioInManager : IServiceObject
{
Result ListAudioIns(out int count, Span<DeviceName> names);
Result OpenAudioIn(
out AudioOutputConfiguration outputConfig,
out IAudioIn audioIn,
Span<DeviceName> outName,
AudioInputConfiguration parameter,
AppletResourceUserId appletResourceId,
int processHandle,
ReadOnlySpan<DeviceName> name,
ulong pid);
Result ListAudioInsAuto(out int count, Span<DeviceName> names);
Result OpenAudioInAuto(
out AudioOutputConfiguration outputConfig,
out IAudioIn audioIn,
Span<DeviceName> outName,
AudioInputConfiguration parameter,
AppletResourceUserId appletResourceId,
int processHandle,
ReadOnlySpan<DeviceName> name,
ulong pid);
Result ListAudioInsAutoFiltered(out int count, Span<DeviceName> names);
Result OpenAudioInProtocolSpecified(
out AudioOutputConfiguration outputConfig,
out IAudioIn audioIn,
Span<DeviceName> outName,
AudioInProtocol protocol,
AudioInputConfiguration parameter,
AppletResourceUserId appletResourceId,
int processHandle,
ReadOnlySpan<DeviceName> name,
ulong pid);
}
}

View File

@@ -0,0 +1,25 @@
using Ryujinx.Audio.Common;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IAudioOut : IServiceObject
{
Result GetAudioOutState(out AudioDeviceState state);
Result Start();
Result Stop();
Result AppendAudioOutBuffer(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer);
Result RegisterBufferEvent(out int eventHandle);
Result GetReleasedAudioOutBuffers(out uint count, Span<ulong> bufferTags);
Result ContainsAudioOutBuffer(out bool contains, ulong bufferTag);
Result AppendAudioOutBufferAuto(ulong bufferTag, ReadOnlySpan<AudioUserBuffer> buffer);
Result GetReleasedAudioOutBuffersAuto(out uint count, Span<ulong> bufferTags);
Result GetAudioOutBufferCount(out uint bufferCount);
Result GetAudioOutPlayedSampleCount(out ulong sampleCount);
Result FlushAudioOutBuffers(out bool pending);
Result SetAudioOutVolume(float volume);
Result GetAudioOutVolume(out float volume);
}
}

View File

@@ -0,0 +1,32 @@
using Ryujinx.Audio.Common;
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IAudioOutManager : IServiceObject
{
Result ListAudioOuts(out int count, Span<DeviceName> names);
Result OpenAudioOut(
out AudioOutputConfiguration outputConfig,
out IAudioOut audioOut,
Span<DeviceName> outName,
AudioInputConfiguration inputConfig,
AppletResourceUserId appletResourceId,
int processHandle,
ReadOnlySpan<DeviceName> name,
ulong pid);
Result ListAudioOutsAuto(out int count, Span<DeviceName> names);
Result OpenAudioOutAuto(
out AudioOutputConfiguration outputConfig,
out IAudioOut audioOut,
Span<DeviceName> outName,
AudioInputConfiguration inputConfig,
AppletResourceUserId appletResourceId,
int processHandle,
ReadOnlySpan<DeviceName> name,
ulong pid);
}
}

View File

@@ -0,0 +1,24 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IAudioRenderer : IServiceObject
{
Result GetSampleRate(out int sampleRate);
Result GetSampleCount(out int sampleCount);
Result GetMixBufferCount(out int mixBufferCount);
Result GetState(out int state);
Result RequestUpdate(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input);
Result Start();
Result Stop();
Result QuerySystemEvent(out int eventHandle);
Result SetRenderingTimeLimit(int percent);
Result GetRenderingTimeLimit(out int percent);
Result RequestUpdateAuto(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input);
Result ExecuteAudioRendererRendering();
Result SetVoiceDropParameter(float voiceDropParameter);
Result GetVoiceDropParameter(out float voiceDropParameter);
}
}

View File

@@ -0,0 +1,29 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IAudioRendererManager : IServiceObject
{
Result OpenAudioRenderer(
out IAudioRenderer renderer,
AudioRendererParameterInternal parameter,
int processHandle,
int workBufferHandle,
ulong workBufferSize,
AppletResourceUserId appletUserId,
ulong pid);
Result GetWorkBufferSize(out long workBufferSize, AudioRendererParameterInternal parameter);
Result GetAudioDeviceService(out IAudioDevice audioDevice, AppletResourceUserId appletUserId);
Result OpenAudioRendererForManualExecution(
out IAudioRenderer renderer,
AudioRendererParameterInternal parameter,
ulong workBufferAddress,
int processHandle,
ulong workBufferSize,
AppletResourceUserId appletUserId,
ulong pid);
Result GetAudioDeviceServiceWithRevisionInfo(out IAudioDevice audioDevice, AppletResourceUserId appletUserId, uint revision);
}
}

View File

@@ -0,0 +1,12 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IAudioSnoopManager : IServiceObject
{
Result EnableDspUsageMeasurement();
Result DisableDspUsageMeasurement();
Result GetDspUsage(out uint usage);
}
}

View File

@@ -0,0 +1,22 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Sf;
using System;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IFinalOutputRecorder : IServiceObject
{
Result GetFinalOutputRecorderState(out uint state);
Result Start();
Result Stop();
Result AppendFinalOutputRecorderBuffer(ReadOnlySpan<byte> buffer, ulong bufferClientPtr);
Result RegisterBufferEvent(out int eventHandle);
Result GetReleasedFinalOutputRecorderBuffers(Span<byte> buffer, out uint count, out ulong released);
Result ContainsFinalOutputRecorderBuffer(ulong bufferPointer, out bool contains);
Result GetFinalOutputRecorderBufferEndTime(ulong bufferPointer, out ulong released);
Result AppendFinalOutputRecorderBufferAuto(ReadOnlySpan<byte> buffer, ulong bufferClientPtr);
Result GetReleasedFinalOutputRecorderBuffersAuto(Span<byte> buffer, out uint count, out ulong released);
Result FlushFinalOutputRecorderBuffers(out bool pending);
Result AttachWorkBuffer(FinalOutputRecorderParameterInternal parameter);
}
}

View File

@@ -0,0 +1,16 @@
using Ryujinx.Horizon.Common;
using Ryujinx.Horizon.Sdk.Applet;
using Ryujinx.Horizon.Sdk.Sf;
namespace Ryujinx.Horizon.Sdk.Audio.Detail
{
interface IFinalOutputRecorderManager : IServiceObject
{
Result OpenFinalOutputRecorder(
out IFinalOutputRecorder recorder,
FinalOutputRecorderParameter parameter,
int processHandle,
out FinalOutputRecorderParameterInternal outParameter,
AppletResourceUserId appletResourceId);
}
}