diff options
Diffstat (limited to 'src/Ryujinx.HLE/HOS/Services/Audio')
45 files changed, 2944 insertions, 0 deletions
diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs new file mode 100644 index 00000000..ee85ded9 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs @@ -0,0 +1,108 @@ +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 AudioInputSystem _system; + private uint _processHandle; + private 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) + { + return ((AudioKernelEvent)outEvent).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(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs new file mode 100644 index 00000000..a80b9402 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs @@ -0,0 +1,204 @@ +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 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(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs new file mode 100644 index 00000000..b5073fce --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs @@ -0,0 +1,34 @@ +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(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs new file mode 100644 index 00000000..2d342206 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs @@ -0,0 +1,41 @@ +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 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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs new file mode 100644 index 00000000..755caee5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs @@ -0,0 +1,235 @@ +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 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; + ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + 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(); + (ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22(); + + 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). + bool protocolSpecifiedRelated = context.RequestData.ReadUInt64() == 1; + + 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; + ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + 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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs new file mode 100644 index 00000000..f2588452 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs @@ -0,0 +1,108 @@ +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 AudioOutputSystem _system; + private uint _processHandle; + private 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) + { + return ((AudioKernelEvent)outEvent).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(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs new file mode 100644 index 00000000..329e1794 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs @@ -0,0 +1,185 @@ +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 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(); + } + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs new file mode 100644 index 00000000..8533d3c5 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs @@ -0,0 +1,33 @@ +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(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs new file mode 100644 index 00000000..7b289196 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs @@ -0,0 +1,41 @@ +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 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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs new file mode 100644 index 00000000..7c5d8c4e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs @@ -0,0 +1,162 @@ +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 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; + ulong deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + 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(); + (ulong deviceNameOutputPosition, ulong deviceNameOutputSize) = context.Request.GetBufferType0x22(); + + 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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs new file mode 100644 index 00000000..724a1e9e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDevice.cs @@ -0,0 +1,172 @@ +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 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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs new file mode 100644 index 00000000..e7a75121 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioDeviceServer.cs @@ -0,0 +1,320 @@ +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 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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs new file mode 100644 index 00000000..55bf29ae --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs @@ -0,0 +1,25 @@ +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(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs new file mode 100644 index 00000000..5b682bf8 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs @@ -0,0 +1,122 @@ +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 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) + { + 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(); + } + } + + public void SetVoiceDropParameter(float voiceDropParameter) + { + _impl.SetVoiceDropParameter(voiceDropParameter); + } + + public float GetVoiceDropParameter() + { + return _impl.GetVoiceDropParameter(); + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs new file mode 100644 index 00000000..a137c413 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs @@ -0,0 +1,217 @@ +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 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.Shared.RentCleared(outputSize)) + using (IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Shared.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(); + } + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs new file mode 100644 index 00000000..1918a977 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioDevice.cs @@ -0,0 +1,18 @@ +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(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs new file mode 100644 index 00000000..404bf4c1 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/IAudioRenderer.cs @@ -0,0 +1,22 @@ +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(); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs new file mode 100644 index 00000000..40e71a43 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManager.cs @@ -0,0 +1,67 @@ +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 AudioRendererManagerImpl _impl; + private 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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs new file mode 100644 index 00000000..80b54e8c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/AudioRendererManagerServer.cs @@ -0,0 +1,116 @@ +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 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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs new file mode 100644 index 00000000..b77fc4b0 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/Decoder.cs @@ -0,0 +1,27 @@ +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(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs new file mode 100644 index 00000000..944541cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/DecoderCommon.cs @@ -0,0 +1,92 @@ +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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs new file mode 100644 index 00000000..9047c266 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IDecoder.cs @@ -0,0 +1,11 @@ +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(); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs new file mode 100644 index 00000000..e94b31ca --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/IHardwareOpusDecoder.cs @@ -0,0 +1,116 @@ +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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs new file mode 100644 index 00000000..23721d3b --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/HardwareOpusDecoderManager/MultiSampleDecoder.cs @@ -0,0 +1,28 @@ +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(); + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs new file mode 100644 index 00000000..1bd2e31d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioController.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audctl")] + class IAudioController : IpcService + { + public IAudioController(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs new file mode 100644 index 00000000..9bbe5b0e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs @@ -0,0 +1,12 @@ +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); + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs new file mode 100644 index 00000000..37d9a8fe --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audin:a")] + class IAudioInManagerForApplet : IpcService + { + public IAudioInManagerForApplet(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs new file mode 100644 index 00000000..1a497efb --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioInManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audin:d")] + class IAudioInManagerForDebugger : IpcService + { + public IAudioInManagerForDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs new file mode 100644 index 00000000..70e60d2e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs @@ -0,0 +1,12 @@ +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); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs new file mode 100644 index 00000000..4b41b0cf --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audout:a")] + class IAudioOutManagerForApplet : IpcService + { + public IAudioOutManagerForApplet(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs new file mode 100644 index 00000000..41cde972 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audout:d")] + class IAudioOutManagerForDebugger : IpcService + { + public IAudioOutManagerForDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs new file mode 100644 index 00000000..642e2525 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManager.cs @@ -0,0 +1,19 @@ +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); + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs new file mode 100644 index 00000000..ca5768cc --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:a")] + class IAudioRendererManagerForApplet : IpcService + { + public IAudioRendererManagerForApplet(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs new file mode 100644 index 00000000..a970ae45 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioRendererManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audren:d")] + class IAudioRendererManagerForDebugger : IpcService + { + public IAudioRendererManagerForDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs new file mode 100644 index 00000000..59e3ad09 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IAudioSnoopManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("auddev")] // 6.0.0+ + class IAudioSnoopManager : IpcService + { + public IAudioSnoopManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs new file mode 100644 index 00000000..01435008 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManager.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:u")] + class IFinalOutputRecorderManager : IpcService + { + public IFinalOutputRecorderManager(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs new file mode 100644 index 00000000..d8fd270d --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForApplet.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:a")] + class IFinalOutputRecorderManagerForApplet : IpcService + { + public IFinalOutputRecorderManagerForApplet(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs new file mode 100644 index 00000000..a8ec51ee --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IFinalOutputRecorderManagerForDebugger.cs @@ -0,0 +1,8 @@ +namespace Ryujinx.HLE.HOS.Services.Audio +{ + [Service("audrec:d")] + class IFinalOutputRecorderManagerForDebugger : IpcService + { + public IFinalOutputRecorderManagerForDebugger(ServiceCtx context) { } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs new file mode 100644 index 00000000..8df8a38c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/IHardwareOpusDecoderManager.cs @@ -0,0 +1,205 @@ +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; + } + + 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; + } + } +}
\ No newline at end of file diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs b/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs new file mode 100644 index 00000000..fd2091c2 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/ResultCode.cs @@ -0,0 +1,21 @@ +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 + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs new file mode 100644 index 00000000..e49c294c --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusDecoderFlags.cs @@ -0,0 +1,11 @@ +using System; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [Flags] + enum OpusDecoderFlags : uint + { + None, + LargeFrameSize = 1 << 0, + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs new file mode 100644 index 00000000..fd63a4f7 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParameters.cs @@ -0,0 +1,15 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x110)] + struct OpusMultiStreamParameters + { + public int SampleRate; + public int ChannelsCount; + public int NumberOfStreams; + public int NumberOfStereoStreams; + public Array64<uint> ChannelMappings; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs new file mode 100644 index 00000000..1315c734 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusMultiStreamParametersEx.cs @@ -0,0 +1,19 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.Types +{ + [StructLayout(LayoutKind.Sequential, Size = 0x118)] + struct OpusMultiStreamParametersEx + { + public int SampleRate; + public int ChannelsCount; + public int NumberOfStreams; + public int NumberOfStereoStreams; + public OpusDecoderFlags Flags; + + Array4<byte> Padding1; + + public Array64<uint> ChannelMappings; + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs new file mode 100644 index 00000000..5ae0eb1e --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusPacketHeader.cs @@ -0,0 +1,23 @@ +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; + } + } +} diff --git a/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs new file mode 100644 index 00000000..f088ed01 --- /dev/null +++ b/src/Ryujinx.HLE/HOS/Services/Audio/Types/OpusParametersEx.cs @@ -0,0 +1,15 @@ +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; + } +} |
