Amadeus: Final Act (#1481)

* Amadeus: Final Act

This is my requiem, I present to you Amadeus, a complete reimplementation of the Audio Renderer!

This reimplementation is based on my reversing of every version of the audio system module that I carried for the past 10 months.
This supports every revision (at the time of writing REV1 to REV8 included) and all features proposed by the Audio Renderer on real hardware.

Because this component could be used outside an emulation context, and to avoid possible "inspirations" not crediting the project, I decided to license the Ryujinx.Audio.Renderer project under LGPLv3.

- FE3H voices in videos and chapter intro are not present.
- Games that use two audio renderer **at the same time** are probably going to have issues right now **until we rewrite the audio output interface** (Crash Team Racing is the only known game to use two renderer at the same time).

- Persona 5 Scrambler now goes ingame but audio is garbage. This is caused by the fact that the game engine is syncing audio and video in a really aggressive way. This will disappears the day this game run at full speed.

* Make timing more precise when sleeping on Windows

Improve precision to a 1ms resolution on Windows NT based OS.
This is used to avoid having totally erratic timings and unify all
Windows users to the same resolution.

NOTE: This is only active when emulation is running.
This commit is contained in:
Mary 2020-08-18 03:49:37 +02:00 committed by GitHub
parent 2a314f3c28
commit a389dd59bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
250 changed files with 23691 additions and 1512 deletions

View file

@ -0,0 +1,98 @@
using Ryujinx.Audio;
using Ryujinx.Audio.Renderer;
using Ryujinx.Audio.Renderer.Integration;
using System;
using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
public class AalHardwareDevice : HardwareDevice
{
private IAalOutput _output;
private int _trackId;
private int _bufferTag;
private int _nextTag;
private AutoResetEvent _releaseEvent;
private uint _channelCount;
private uint _sampleRate;
private short[] _buffer;
private Queue<long> _releasedTags;
public AalHardwareDevice(int bufferTag, IAalOutput output, uint channelCount, uint sampleRate)
{
_bufferTag = bufferTag;
_channelCount = channelCount;
_sampleRate = sampleRate;
_output = output;
_releaseEvent = new AutoResetEvent(true);
_trackId = _output.OpenTrack((int)sampleRate, (int)channelCount, AudioCallback);
_releasedTags = new Queue<long>();
_buffer = new short[RendererConstants.TargetSampleCount * channelCount];
_output.Start(_trackId);
}
private void AudioCallback()
{
long[] released = _output.GetReleasedBuffers(_trackId, int.MaxValue);
lock (_releasedTags)
{
foreach (long tag in released)
{
_releasedTags.Enqueue(tag);
}
}
}
private long GetReleasedTag()
{
lock (_releasedTags)
{
if (_releasedTags.Count > 0)
{
return _releasedTags.Dequeue();
}
return (_bufferTag << 16) | (_nextTag++);
}
}
public void AppendBuffer(ReadOnlySpan<short> data, uint channelCount)
{
data.CopyTo(_buffer.AsSpan());
_output.AppendBuffer(_trackId, GetReleasedTag(), _buffer);
}
public uint GetChannelCount()
{
return _channelCount;
}
public uint GetSampleRate()
{
return _sampleRate;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_output.Stop(_trackId);
_output.CloseTrack(_trackId);
_releaseEvent.Dispose();
}
}
}
}

View file

@ -0,0 +1,158 @@
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 VirtualDeviceSession[] _sessions;
private ulong _appletResourceId;
private int _revision;
private bool _isUsbDeviceSupported;
private VirtualDeviceSessionRegistry _registry;
private KEvent _systemEvent;
public AudioDevice(VirtualDeviceSessionRegistry registry, KernelContext context, ulong appletResourceId, int revision)
{
_registry = registry;
_appletResourceId = appletResourceId;
_revision = revision;
BehaviourContext behaviourContext = new BehaviourContext();
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 ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume)
{
if (TryGetDeviceByName(out VirtualDeviceSession result, name, true))
{
systemMasterVolume = result.Device.MasterVolume;
}
else
{
systemMasterVolume = 0.0f;
}
return ResultCode.Success;
}
public string[] ListAudioDeviceName()
{
int deviceCount = _sessions.Length;
if (!_isUsbDeviceSupported)
{
deviceCount--;
}
string[] result = new string[deviceCount];
for (int i = 0; i < deviceCount; i++)
{
result[i] = _sessions[i].Device.Name;
}
return result;
}
public KEvent QueryAudioDeviceInputEvent()
{
return _systemEvent;
}
public KEvent QueryAudioDeviceOutputEvent()
{
return _systemEvent;
}
public KEvent QueryAudioDeviceSystemEvent()
{
return _systemEvent;
}
}
}

View file

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

View file

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

View file

@ -0,0 +1,112 @@
using Ryujinx.Audio.Renderer.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 AudioRenderSystem _impl;
public AudioRenderer(AudioRenderSystem impl)
{
_impl = impl;
}
public ResultCode ExecuteAudioRendererRendering()
{
throw new NotImplementedException();
}
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)
{
systemEvent = ((AudioKernelEvent)outEvent).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();
}
}
}
}

View file

@ -0,0 +1,188 @@
using Ryujinx.Common.Logging;
using Ryujinx.HLE.HOS.Ipc;
using Ryujinx.HLE.HOS.Kernel.Common;
using Ryujinx.HLE.HOS.Kernel.Threading;
using System;
using System.Buffers;
namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer
{
class AudioRendererServer : IpcService, IDisposable
{
private IAudioRenderer _impl;
public AudioRendererServer(IAudioRenderer impl)
{
_impl = impl;
}
[Command(0)]
// GetSampleRate() -> u32
public ResultCode GetSampleRate(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetSampleRate());
return ResultCode.Success;
}
[Command(1)]
// GetSampleCount() -> u32
public ResultCode GetSampleCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetSampleCount());
return ResultCode.Success;
}
[Command(2)]
// GetMixBufferCount() -> u32
public ResultCode GetMixBufferCount(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetMixBufferCount());
return ResultCode.Success;
}
[Command(3)]
// GetState() -> u32
public ResultCode GetState(ServiceCtx context)
{
context.ResponseData.Write(_impl.GetState());
return ResultCode.Success;
}
[Command(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)
{
long inputPosition = context.Request.SendBuff[0].Position;
long inputSize = context.Request.SendBuff[0].Size;
long outputPosition = context.Request.ReceiveBuff[0].Position;
long outputSize = context.Request.ReceiveBuff[0].Size;
long performanceOutputPosition = context.Request.ReceiveBuff[1].Position;
long performanceOutputSize = context.Request.ReceiveBuff[1].Size;
ReadOnlyMemory<byte> input = context.Memory.GetSpan((ulong)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((ulong)outputPosition, output.Span);
context.Memory.Write((ulong)performanceOutputPosition, performanceOutput.Span);
}
else
{
Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{result}");
}
return result;
}
[Command(5)]
// Start()
public ResultCode Start(ServiceCtx context)
{
return _impl.Start();
}
[Command(6)]
// Stop()
public ResultCode Stop(ServiceCtx context)
{
return _impl.Stop();
}
[Command(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) != KernelResult.Success)
{
throw new InvalidOperationException("Out of handles!");
}
context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle);
}
return result;
}
[Command(8)]
// SetAudioRendererRenderingTimeLimit(u32 limit)
public ResultCode SetAudioRendererRenderingTimeLimit(ServiceCtx context)
{
uint limit = context.RequestData.ReadUInt32();
_impl.SetRenderingTimeLimit(limit);
return ResultCode.Success;
}
[Command(9)]
// GetAudioRendererRenderingTimeLimit() -> u32 limit
public ResultCode GetAudioRendererRenderingTimeLimit(ServiceCtx context)
{
uint limit = _impl.GetRenderingTimeLimit();
context.ResponseData.Write(limit);
return ResultCode.Success;
}
[Command(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)
{
(long inputPosition, long inputSize) = context.Request.GetBufferType0x21();
(long outputPosition, long outputSize) = context.Request.GetBufferType0x22(0);
(long performanceOutputPosition, long performanceOutputSize) = context.Request.GetBufferType0x22(1);
ReadOnlyMemory<byte> input = context.Memory.GetSpan((ulong)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((ulong)outputPosition, output.Span);
context.Memory.Write((ulong)performanceOutputPosition, performanceOutput.Span);
}
return result;
}
public void Dispose()
{
Dispose(true);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
_impl.Dispose();
}
}
}
}

View file

@ -0,0 +1,17 @@
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();
ResultCode GetAudioSystemMasterVolumeSetting(string name, out float systemMasterVolume);
}
}

View file

@ -0,0 +1,20 @@
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();
}
}