diff options
author | Mary <me@thog.eu> | 2021-02-26 01:11:56 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-26 01:11:56 +0100 |
commit | f556c80d0230056335632b60c71f1567e177239e (patch) | |
tree | 748aa6be62b93a8e941e25dbd83f39e1dbb37035 /Ryujinx.HLE | |
parent | 1c49089ff00fc87dc4872f135dc6a0d36169a970 (diff) |
Haydn: Part 1 (#2007)
* Haydn: Part 1
Based on my reverse of audio 11.0.0.
As always, core implementation under LGPLv3 for the same reasons as for Amadeus.
This place the bases of a more flexible audio system while making audout & audin accurate.
This have the following improvements:
- Complete reimplementation of audout and audin.
- Audin currently only have a dummy backend.
- Dramatically reduce CPU usage by up to 50% in common cases (SoundIO and OpenAL).
- Audio Renderer now can output to 5.1 devices when supported.
- Audio Renderer init its backend on demand instead of keeping two up all the time.
- All backends implementation are now in their own project.
- Ryujinx.Audio.Renderer was renamed Ryujinx.Audio and was refactored because of this.
As a note, games having issues with OpenAL haven't improved and will not
because of OpenAL design (stopping when buffers finish playing causing
possible audio "pops" when buffers are very small).
* Update for latest hexkyz's edits on Switchbrew
* audren: Rollback channel configuration changes
* Address gdkchan's comments
* Fix typo in OpenAL backend driver
* Address last comments
* Fix a nit
* Address gdkchan's comments
Diffstat (limited to 'Ryujinx.HLE')
21 files changed, 1223 insertions, 597 deletions
diff --git a/Ryujinx.HLE/HOS/Horizon.cs b/Ryujinx.HLE/HOS/Horizon.cs index 9309ae41..16b4c376 100644 --- a/Ryujinx.HLE/HOS/Horizon.cs +++ b/Ryujinx.HLE/HOS/Horizon.cs @@ -2,9 +2,11 @@ using LibHac; using LibHac.Bcat; using LibHac.Fs; using LibHac.FsSystem; -using Ryujinx.Audio.Renderer; +using Ryujinx.Audio; +using Ryujinx.Audio.Input; +using Ryujinx.Audio.Integration; +using Ryujinx.Audio.Output; using Ryujinx.Audio.Renderer.Device; -using Ryujinx.Audio.Renderer.Integration; using Ryujinx.Audio.Renderer.Server; using Ryujinx.Common; using Ryujinx.Configuration; @@ -51,6 +53,9 @@ namespace Ryujinx.HLE.HOS internal Switch Device { get; private set; } internal SurfaceFlinger SurfaceFlinger { get; private set; } + internal AudioManager AudioManager { get; private set; } + internal AudioOutputManager AudioOutputManager { get; private set; } + internal AudioInputManager AudioInputManager { get; private set; } internal AudioRendererManager AudioRendererManager { get; private set; } internal VirtualDeviceSessionRegistry AudioDeviceSessionRegistry { get; private set; } @@ -206,29 +211,48 @@ namespace Ryujinx.HLE.HOS private void InitializeAudioRenderer() { + AudioManager = new AudioManager(); + AudioOutputManager = new AudioOutputManager(); + AudioInputManager = new AudioInputManager(); AudioRendererManager = new AudioRendererManager(); AudioDeviceSessionRegistry = new VirtualDeviceSessionRegistry(); - IWritableEvent[] writableEvents = new IWritableEvent[RendererConstants.AudioRendererSessionCountMax]; + IWritableEvent[] audioOutputRegisterBufferEvents = new IWritableEvent[Constants.AudioOutSessionCountMax]; - for (int i = 0; i < writableEvents.Length; i++) + for (int i = 0; i < audioOutputRegisterBufferEvents.Length; i++) { - KEvent systemEvent = new KEvent(KernelContext); + KEvent registerBufferEvent = new KEvent(KernelContext); - writableEvents[i] = new AudioKernelEvent(systemEvent); + audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); } - HardwareDevice[] devices = new HardwareDevice[RendererConstants.AudioRendererSessionCountMax]; + AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents); + + IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax]; - // TODO: don't hardcode those values. - // TODO: keep the device somewhere and dispose it when exiting. - // TODO: This is kind of wrong, we should have an high level API for that and mix all buffers between them. - for (int i = 0; i < devices.Length; i++) + for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++) { - devices[i] = new AalHardwareDevice(i, Device.AudioOut, 2, RendererConstants.TargetSampleRate); + KEvent registerBufferEvent = new KEvent(KernelContext); + + audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent); } - AudioRendererManager.Initialize(writableEvents, devices); + AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents); + + IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax]; + + for (int i = 0; i < systemEvents.Length; i++) + { + KEvent systemEvent = new KEvent(KernelContext); + + systemEvents[i] = new AudioKernelEvent(systemEvent); + } + + AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update); + + AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver); + + AudioManager.Start(); } public void InitializeServices() @@ -363,6 +387,10 @@ namespace Ryujinx.HLE.HOS // This is safe as KThread that are likely to call ioctls are going to be terminated by the post handler hook on the SVC facade. INvDrvServices.Destroy(); + AudioManager.Dispose(); + AudioOutputManager.Dispose(); + AudioInputManager.Dispose(); + AudioRendererManager.Dispose(); KernelContext.Dispose(); diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs new file mode 100644 index 00000000..ee85ded9 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs new file mode 100644 index 00000000..1a1a3b6e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs @@ -0,0 +1,209 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioIn +{ + class AudioInServer : IpcService, IDisposable + { + private IAudioIn _impl; + + public AudioInServer(IAudioIn impl) + { + _impl = impl; + } + + [Command(0)] + // GetAudioInState() -> u32 state + public ResultCode GetAudioInState(ServiceCtx context) + { + context.ResponseData.Write((uint)_impl.GetState()); + + return ResultCode.Success; + } + + [Command(1)] + // Start() + public ResultCode Start(ServiceCtx context) + { + return _impl.Start(); + } + + [Command(2)] + // Stop() + public ResultCode StopAudioIn(ServiceCtx context) + { + return _impl.Stop(); + } + + [Command(3)] + // AppendAudioInBuffer(u64 tag, buffer<nn::audio::AudioInBuffer, 5>) + public ResultCode AppendAudioInBuffer(ServiceCtx context) + { + long 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); + } + + [Command(4)] + // RegisterBufferEvent() -> handle<copy> + public ResultCode RegisterBufferEvent(ServiceCtx context) + { + KEvent bufferEvent = _impl.RegisterBufferEvent(); + + if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(5)] + // GetReleasedAudioInBuffers() -> (u32 count, buffer<u64, 6> tags) + public ResultCode GetReleasedAudioInBuffers(ServiceCtx context) + { + long position = context.Request.ReceiveBuff[0].Position; + long 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; + } + } + + [Command(6)] + // ContainsAudioInBuffer(u64 tag) -> b8 + public ResultCode ContainsAudioInBuffer(ServiceCtx context) + { + ulong bufferTag = context.RequestData.ReadUInt64(); + + context.ResponseData.Write(_impl.ContainsBuffer(bufferTag)); + + return ResultCode.Success; + } + + [Command(7)] // 3.0.0+ + // AppendUacInBuffer(u64 tag, handle<copy, unknown>, buffer<nn::audio::AudioInBuffer, 5>) + public ResultCode AppendUacInBuffer(ServiceCtx context) + { + long 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); + } + + [Command(8)] // 3.0.0+ + // AppendAudioInBufferAuto(u64 tag, buffer<nn::audio::AudioInBuffer, 0x21>) + public ResultCode AppendAudioInBufferAuto(ServiceCtx context) + { + (long position, _) = context.Request.GetBufferType0x21(); + + ulong bufferTag = context.RequestData.ReadUInt64(); + + AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position); + + return _impl.AppendBuffer(bufferTag, ref data); + } + + [Command(9)] // 3.0.0+ + // GetReleasedAudioInBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags) + public ResultCode GetReleasedAudioInBuffersAuto(ServiceCtx context) + { + (long position, long size) = context.Request.GetBufferType0x22(); + + 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; + } + } + + [Command(10)] // 3.0.0+ + // AppendUacInBufferAuto(u64 tag, handle<copy, event>, buffer<nn::audio::AudioInBuffer, 0x21>) + public ResultCode AppendUacInBufferAuto(ServiceCtx context) + { + (long 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); + } + + [Command(11)] // 4.0.0+ + // GetAudioInBufferCount() -> u32 + public ResultCode GetAudioInBufferCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetBufferCount()); + + return ResultCode.Success; + } + + [Command(12)] // 4.0.0+ + // SetAudioInVolume(s32) + public ResultCode SetAudioInVolume(ServiceCtx context) + { + float volume = context.RequestData.ReadSingle(); + + _impl.SetVolume(volume); + + return ResultCode.Success; + } + + [Command(13)] // 4.0.0+ + // GetAudioInVolume() -> s32 + public ResultCode GetAudioInVolume(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetVolume()); + + return ResultCode.Success; + } + + [Command(14)] // 6.0.0+ + // FlushAudioInBuffers() -> b8 + public ResultCode FlushAudioInBuffers(ServiceCtx context) + { + context.ResponseData.Write(_impl.FlushBuffers()); + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs new file mode 100644 index 00000000..b5073fce --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs new file mode 100644 index 00000000..2d342206 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs new file mode 100644 index 00000000..079b91ca --- /dev/null +++ b/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; + } + + [Command(0)] + // ListAudioIns() -> (u32, buffer<bytes, 6>) + public ResultCode ListAudioIns(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioIns(false); + + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioInNameSize - buffer.Length); + + position += AudioInNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(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(); + + long deviceNameInputPosition = context.Request.SendBuff[0].Position; + long deviceNameInputSize = context.Request.SendBuff[0].Size; + + long deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; + long deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, 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((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioInServer(obj)); + } + + return resultCode; + } + + [Command(2)] // 3.0.0+ + // ListAudioInsAuto() -> (u32, buffer<bytes, 0x22>) + public ResultCode ListAudioInsAuto(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioIns(false); + + (long position, long size) = context.Request.GetBufferType0x22(); + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioInNameSize - buffer.Length); + + position += AudioInNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(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(); + + (long deviceNameInputPosition, long deviceNameInputSize) = context.Request.GetBufferType0x21(); + (long deviceNameOutputPosition, long deviceNameOutputSize) = context.Request.GetBufferType0x22(); + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, 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((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioInServer(obj)); + } + + return resultCode; + } + + [Command(4)] // 3.0.0+ + // ListAudioInsAutoFiltered() -> (u32, buffer<bytes, 0x22>) + public ResultCode ListAudioInsAutoFiltered(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioIns(true); + + (long position, long size) = context.Request.GetBufferType0x22(); + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioInNameSize - buffer.Length); + + position += AudioInNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(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(); + + long deviceNameInputPosition = context.Request.SendBuff[0].Position; + long deviceNameInputSize = context.Request.SendBuff[0].Size; + + long deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; + long deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, 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((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioInNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioInServer(obj)); + } + + return resultCode; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs new file mode 100644 index 00000000..f2588452 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs new file mode 100644 index 00000000..4242eb7e --- /dev/null +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs @@ -0,0 +1,190 @@ +using Ryujinx.Audio.Common; +using Ryujinx.Cpu; +using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.HLE.HOS.Kernel.Common; +using Ryujinx.HLE.HOS.Kernel.Threading; +using Ryujinx.Memory; +using System; +using System.Runtime.InteropServices; + +namespace Ryujinx.HLE.HOS.Services.Audio.AudioOut +{ + class AudioOutServer : IpcService, IDisposable + { + private IAudioOut _impl; + + public AudioOutServer(IAudioOut impl) + { + _impl = impl; + } + + [Command(0)] + // GetAudioOutState() -> u32 state + public ResultCode GetAudioOutState(ServiceCtx context) + { + context.ResponseData.Write((uint)_impl.GetState()); + + return ResultCode.Success; + } + + [Command(1)] + // Start() + public ResultCode Start(ServiceCtx context) + { + return _impl.Start(); + } + + [Command(2)] + // Stop() + public ResultCode Stop(ServiceCtx context) + { + return _impl.Stop(); + } + + [Command(3)] + // AppendAudioOutBuffer(u64 bufferTag, buffer<nn::audio::AudioOutBuffer, 5> buffer) + public ResultCode AppendAudioOutBuffer(ServiceCtx context) + { + long 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); + } + + [Command(4)] + // RegisterBufferEvent() -> handle<copy> + public ResultCode RegisterBufferEvent(ServiceCtx context) + { + KEvent bufferEvent = _impl.RegisterBufferEvent(); + + if (context.Process.HandleTable.GenerateHandle(bufferEvent.ReadableEvent, out int handle) != KernelResult.Success) + { + throw new InvalidOperationException("Out of handles!"); + } + + context.Response.HandleDesc = IpcHandleDesc.MakeCopy(handle); + + return ResultCode.Success; + } + + [Command(5)] + // GetReleasedAudioOutBuffers() -> (u32 count, buffer<u64, 6> tags) + public ResultCode GetReleasedAudioOutBuffers(ServiceCtx context) + { + long position = context.Request.ReceiveBuff[0].Position; + long 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; + } + } + + [Command(6)] + // ContainsAudioOutBuffer(u64 tag) -> b8 + public ResultCode ContainsAudioOutBuffer(ServiceCtx context) + { + ulong bufferTag = context.RequestData.ReadUInt64(); + + context.ResponseData.Write(_impl.ContainsBuffer(bufferTag)); + + return ResultCode.Success; + } + + [Command(7)] // 3.0.0+ + // AppendAudioOutBufferAuto(u64 tag, buffer<nn::audio::AudioOutBuffer, 0x21>) + public ResultCode AppendAudioOutBufferAuto(ServiceCtx context) + { + (long position, _) = context.Request.GetBufferType0x21(); + + ulong bufferTag = context.RequestData.ReadUInt64(); + + AudioUserBuffer data = MemoryHelper.Read<AudioUserBuffer>(context.Memory, position); + + return _impl.AppendBuffer(bufferTag, ref data); + } + + [Command(8)] // 3.0.0+ + // GetReleasedAudioOutBuffersAuto() -> (u32 count, buffer<u64, 0x22> tags) + public ResultCode GetReleasedAudioOutBuffersAuto(ServiceCtx context) + { + (long position, long size) = context.Request.GetBufferType0x22(); + + 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; + } + } + + [Command(9)] // 4.0.0+ + // GetAudioOutBufferCount() -> u32 + public ResultCode GetAudioOutBufferCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetBufferCount()); + + return ResultCode.Success; + } + + [Command(10)] // 4.0.0+ + // GetAudioOutPlayedSampleCount() -> u64 + public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetPlayedSampleCount()); + + return ResultCode.Success; + } + + [Command(11)] // 4.0.0+ + // FlushAudioOutBuffers() -> b8 + public ResultCode FlushAudioOutBuffers(ServiceCtx context) + { + context.ResponseData.Write(_impl.FlushBuffers()); + + return ResultCode.Success; + } + + [Command(12)] // 6.0.0+ + // SetAudioOutVolume(s32) + public ResultCode SetAudioOutVolume(ServiceCtx context) + { + float volume = context.RequestData.ReadSingle(); + + _impl.SetVolume(volume); + + return ResultCode.Success; + } + + [Command(13)] // 6.0.0+ + // GetAudioOutVolume() -> s32 + public ResultCode GetAudioOutVolume(ServiceCtx context) + { + context.ResponseData.Write(_impl.GetVolume()); + + return ResultCode.Success; + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _impl.Dispose(); + } + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs new file mode 100644 index 00000000..8533d3c5 --- /dev/null +++ b/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/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs new file mode 100644 index 00000000..29490553 --- /dev/null +++ b/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) + { + 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); + + if (result == ResultCode.Success) + { + obj = new AudioOut.AudioOut(outSystem, context.Device.System.KernelContext, processHandle); + } + else + { + obj = null; + } + + return result; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs deleted file mode 100644 index c941cf4f..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs +++ /dev/null @@ -1,228 +0,0 @@ -using Ryujinx.Audio; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Ipc; -using Ryujinx.HLE.HOS.Kernel; -using Ryujinx.HLE.HOS.Kernel.Common; -using Ryujinx.HLE.HOS.Kernel.Threading; -using System; -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager -{ - class IAudioOut : IpcService, IDisposable - { - private readonly KernelContext _kernelContext; - private readonly IAalOutput _audioOut; - private readonly KEvent _releaseEvent; - private int _releaseEventHandle; - private readonly int _track; - private readonly int _clientHandle; - - public IAudioOut(KernelContext kernelContext, IAalOutput audioOut, KEvent releaseEvent, int track, int clientHandle) - { - _kernelContext = kernelContext; - _audioOut = audioOut; - _releaseEvent = releaseEvent; - _track = track; - _clientHandle = clientHandle; - } - - [Command(0)] - // GetAudioOutState() -> u32 state - public ResultCode GetAudioOutState(ServiceCtx context) - { - context.ResponseData.Write((int)_audioOut.GetState(_track)); - - return ResultCode.Success; - } - - [Command(1)] - // StartAudioOut() - public ResultCode StartAudioOut(ServiceCtx context) - { - _audioOut.Start(_track); - - return ResultCode.Success; - } - - [Command(2)] - // StopAudioOut() - public ResultCode StopAudioOut(ServiceCtx context) - { - _audioOut.Stop(_track); - - return ResultCode.Success; - } - - [Command(3)] - // AppendAudioOutBuffer(u64 tag, buffer<nn::audio::AudioOutBuffer, 5>) - public ResultCode AppendAudioOutBuffer(ServiceCtx context) - { - return AppendAudioOutBufferImpl(context, context.Request.SendBuff[0].Position); - } - - [Command(4)] - // RegisterBufferEvent() -> handle<copy> - public ResultCode RegisterBufferEvent(ServiceCtx context) - { - if (_releaseEventHandle == 0) - { - if (context.Process.HandleTable.GenerateHandle(_releaseEvent.ReadableEvent, out _releaseEventHandle) != KernelResult.Success) - { - throw new InvalidOperationException("Out of handles!"); - } - } - - context.Response.HandleDesc = IpcHandleDesc.MakeCopy(_releaseEventHandle); - - return ResultCode.Success; - } - - [Command(5)] - // GetReleasedAudioOutBuffer() -> (u32 count, buffer<nn::audio::AudioOutBuffer, 6>) - public ResultCode GetReleasedAudioOutBuffer(ServiceCtx context) - { - long position = context.Request.ReceiveBuff[0].Position; - long size = context.Request.ReceiveBuff[0].Size; - - return GetReleasedAudioOutBufferImpl(context, position, size); - } - - [Command(6)] - // ContainsAudioOutBuffer(u64 tag) -> b8 - public ResultCode ContainsAudioOutBuffer(ServiceCtx context) - { - long tag = context.RequestData.ReadInt64(); - - context.ResponseData.Write(_audioOut.ContainsBuffer(_track, tag)); - - return ResultCode.Success; - } - - [Command(7)] // 3.0.0+ - // AppendAudioOutBufferAuto(u64 tag, buffer<nn::audio::AudioOutBuffer, 0x21>) - public ResultCode AppendAudioOutBufferAuto(ServiceCtx context) - { - (long position, _) = context.Request.GetBufferType0x21(); - - return AppendAudioOutBufferImpl(context, position); - } - - public ResultCode AppendAudioOutBufferImpl(ServiceCtx context, long position) - { - long tag = context.RequestData.ReadInt64(); - - AudioOutData data = MemoryHelper.Read<AudioOutData>(context.Memory, position); - - // NOTE: Assume PCM16 all the time, change if new format are found. - short[] buffer = new short[data.SampleBufferSize / sizeof(short)]; - - context.Process.HandleTable.GetKProcess(_clientHandle).CpuMemory.Read((ulong)data.SampleBufferPtr, MemoryMarshal.Cast<short, byte>(buffer)); - - _audioOut.AppendBuffer(_track, tag, buffer); - - return ResultCode.Success; - } - - [Command(8)] // 3.0.0+ - // GetReleasedAudioOutBufferAuto() -> (u32 count, buffer<nn::audio::AudioOutBuffer, 0x22>) - public ResultCode GetReleasedAudioOutBufferAuto(ServiceCtx context) - { - (long position, long size) = context.Request.GetBufferType0x22(); - - return GetReleasedAudioOutBufferImpl(context, position, size); - } - - public ResultCode GetReleasedAudioOutBufferImpl(ServiceCtx context, long position, long size) - { - uint count = (uint)((ulong)size >> 3); - - long[] releasedBuffers = _audioOut.GetReleasedBuffers(_track, (int)count); - - for (uint index = 0; index < count; index++) - { - long tag = 0; - - if (index < releasedBuffers.Length) - { - tag = releasedBuffers[index]; - } - - context.Memory.Write((ulong)(position + index * 8), tag); - } - - context.ResponseData.Write(releasedBuffers.Length); - - return ResultCode.Success; - } - - [Command(9)] // 4.0.0+ - // GetAudioOutBufferCount() -> u32 - public ResultCode GetAudioOutBufferCount(ServiceCtx context) - { - uint bufferCount = _audioOut.GetBufferCount(_track); - - context.ResponseData.Write(bufferCount); - - return ResultCode.Success; - } - - [Command(10)] // 4.0.0+ - // GetAudioOutPlayedSampleCount() -> u64 - public ResultCode GetAudioOutPlayedSampleCount(ServiceCtx context) - { - ulong playedSampleCount = _audioOut.GetPlayedSampleCount(_track); - - context.ResponseData.Write(playedSampleCount); - - return ResultCode.Success; - } - - [Command(11)] // 4.0.0+ - // FlushAudioOutBuffers() -> b8 - public ResultCode FlushAudioOutBuffers(ServiceCtx context) - { - bool heldBuffers = _audioOut.FlushBuffers(_track); - - context.ResponseData.Write(heldBuffers); - - return ResultCode.Success; - } - - [Command(12)] // 6.0.0+ - // SetAudioOutVolume(s32) - public ResultCode SetAudioOutVolume(ServiceCtx context) - { - float volume = context.RequestData.ReadSingle(); - - _audioOut.SetVolume(_track, volume); - - return ResultCode.Success; - } - - [Command(13)] // 6.0.0+ - // GetAudioOutVolume() -> s32 - public ResultCode GetAudioOutVolume(ServiceCtx context) - { - float volume = _audioOut.GetVolume(_track); - - context.ResponseData.Write(volume); - - return ResultCode.Success; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _kernelContext.Syscall.CloseHandle(_clientHandle); - _audioOut.CloseTrack(_track); - } - } - } -}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs deleted file mode 100644 index 2598d0f8..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/Types/AudioOutData.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Runtime.InteropServices; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager -{ - [StructLayout(LayoutKind.Sequential)] - struct AudioOutData - { - public long NextBufferPtr; - public long SampleBufferPtr; - public long SampleBufferCapacity; - public long SampleBufferSize; - public long SampleBufferInnerOffset; - } -}
\ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs new file mode 100644 index 00000000..13930d31 --- /dev/null +++ b/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; + } + + [Command(0)] + // ListAudioOuts() -> (u32, buffer<bytes, 6>) + public ResultCode ListAudioOuts(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioOuts(); + + long position = context.Request.ReceiveBuff[0].Position; + long size = context.Request.ReceiveBuff[0].Size; + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioOutNameSize - buffer.Length); + + position += AudioOutNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(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(); + + long deviceNameInputPosition = context.Request.SendBuff[0].Position; + long deviceNameInputSize = context.Request.SendBuff[0].Size; + + long deviceNameOutputPosition = context.Request.ReceiveBuff[0].Position; + long deviceNameOutputSize = context.Request.ReceiveBuff[0].Size; + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, deviceNameInputSize); + + ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(outputConfiguration); + + byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); + + context.Memory.Write((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioOutServer(obj)); + } + + return resultCode; + } + + [Command(2)] // 3.0.0+ + // ListAudioOutsAuto() -> (u32, buffer<bytes, 0x22>) + public ResultCode ListAudioOutsAuto(ServiceCtx context) + { + string[] deviceNames = _impl.ListAudioOuts(); + + (long position, long size) = context.Request.GetBufferType0x22(); + + long basePosition = position; + + int count = 0; + + foreach (string name in deviceNames) + { + byte[] buffer = Encoding.ASCII.GetBytes(name); + + if ((position - basePosition) + buffer.Length > size) + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); + + break; + } + + context.Memory.Write((ulong)position, buffer); + MemoryHelper.FillWithZeros(context.Memory, position + buffer.Length, AudioOutNameSize - buffer.Length); + + position += AudioOutNameSize; + count++; + } + + context.ResponseData.Write(count); + + return ResultCode.Success; + } + + [Command(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(); + + (long deviceNameInputPosition, long deviceNameInputSize) = context.Request.GetBufferType0x21(); + (long deviceNameOutputPosition, long deviceNameOutputSize) = context.Request.GetBufferType0x22(); + + uint processHandle = (uint)context.Request.HandleDesc.ToCopy[0]; + + string inputDeviceName = MemoryHelper.ReadAsciiString(context.Memory, deviceNameInputPosition, deviceNameInputSize); + + ResultCode resultCode = _impl.OpenAudioOut(context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, inputDeviceName, ref inputConfiguration, appletResourceUserId, processHandle); + + if (resultCode == ResultCode.Success) + { + context.ResponseData.WriteStruct(outputConfiguration); + + byte[] outputDeviceNameRaw = Encoding.ASCII.GetBytes(outputDeviceName); + + context.Memory.Write((ulong)deviceNameOutputPosition, outputDeviceNameRaw); + MemoryHelper.FillWithZeros(context.Memory, deviceNameOutputPosition + outputDeviceNameRaw.Length, AudioOutNameSize - outputDeviceNameRaw.Length); + + MakeObject(context, new AudioOutServer(obj)); + } + + return resultCode; + } + } +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs deleted file mode 100644 index fdc23604..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AalHardwareDevice.cs +++ /dev/null @@ -1,98 +0,0 @@ -using Ryujinx.Audio; -using Ryujinx.Audio.Renderer; -using Ryujinx.Audio.Renderer.Integration; -using System; -using System.Collections.Generic; -using System.Threading; - -namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer -{ - public class AalHardwareDevice : HardwareDevice - { - private IAalOutput _output; - private int _trackId; - private int _bufferTag; - private int _nextTag; - private AutoResetEvent _releaseEvent; - - private uint _channelCount; - private uint _sampleRate; - - private short[] _buffer; - - private Queue<long> _releasedTags; - - public AalHardwareDevice(int bufferTag, IAalOutput output, uint channelCount, uint sampleRate) - { - _bufferTag = bufferTag; - _channelCount = channelCount; - _sampleRate = sampleRate; - _output = output; - _releaseEvent = new AutoResetEvent(true); - _trackId = _output.OpenTrack((int)sampleRate, (int)channelCount, AudioCallback); - _releasedTags = new Queue<long>(); - - _buffer = new short[RendererConstants.TargetSampleCount * channelCount]; - - _output.Start(_trackId); - } - - private void AudioCallback() - { - long[] released = _output.GetReleasedBuffers(_trackId, int.MaxValue); - - lock (_releasedTags) - { - foreach (long tag in released) - { - _releasedTags.Enqueue(tag); - } - } - } - - private long GetReleasedTag() - { - lock (_releasedTags) - { - if (_releasedTags.Count > 0) - { - return _releasedTags.Dequeue(); - } - - return (_bufferTag << 16) | (_nextTag++); - } - } - - public void AppendBuffer(ReadOnlySpan<short> data, uint channelCount) - { - data.CopyTo(_buffer.AsSpan()); - - _output.AppendBuffer(_trackId, GetReleasedTag(), _buffer); - } - - public uint GetChannelCount() - { - return _channelCount; - } - - public uint GetSampleRate() - { - return _sampleRate; - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool disposing) - { - if (disposing) - { - _output.Stop(_trackId); - _output.CloseTrack(_trackId); - _releaseEvent.Dispose(); - } - } - } -} diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs index 1a132b91..55bf29ae 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioKernelEvent.cs @@ -1,4 +1,4 @@ -using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Integration; using Ryujinx.HLE.HOS.Kernel.Threading; namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs index 702648dd..d69bde03 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRenderer.cs @@ -1,4 +1,4 @@ -using Ryujinx.Audio.Renderer.Integration; +using Ryujinx.Audio.Integration; using Ryujinx.Audio.Renderer.Server; using Ryujinx.HLE.HOS.Kernel.Threading; using System; diff --git a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs index b3f7f5e0..9bbe5b0e 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioInManager.cs @@ -1,87 +1,12 @@ -using Ryujinx.Cpu; -using Ryujinx.Memory; -using System; -using System.Text; +using Ryujinx.Audio.Common; +using Ryujinx.HLE.HOS.Services.Audio.AudioIn; namespace Ryujinx.HLE.HOS.Services.Audio { - [Service("audin:u")] - class IAudioInManager : IpcService + interface IAudioInManager { - private const string DefaultAudioInsName = "BuiltInHeadset"; + public string[] ListAudioIns(bool filtered); - public IAudioInManager(ServiceCtx context) { } - - [Command(0)] - // ListAudioIns() -> (u32 count, buffer<bytes, 6> names) - public ResultCode ListAudioIns(ServiceCtx context) - { - long bufferPosition = context.Request.ReceiveBuff[0].Position; - long bufferSize = context.Request.ReceiveBuff[0].Size; - - // NOTE: The service check if AudioInManager thread is started, if not it starts it. - - uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, false); - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [Command(2)] // 3.0.0+ - // ListAudioInsAuto() -> (u32 count, buffer<bytes, 0x22> names) - public ResultCode ListAudioInsAuto(ServiceCtx context) - { - (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22(); - - // NOTE: The service check if AudioInManager thread is started, if not it starts it. - - uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, false); - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - [Command(4)] // 3.0.0+ - // ListAudioInsAutoFiltered() -> (u32 count, buffer<bytes, 0x22> names) - public ResultCode ListAudioInsAutoFiltered(ServiceCtx context) - { - (long bufferPosition, long bufferSize) = context.Request.GetBufferType0x22(); - - // NOTE: The service check if AudioInManager thread is started, if not it starts it. - - uint count = ListAudioInsImpl(context.Memory, bufferPosition, bufferSize, true); - - context.ResponseData.Write(count); - - return ResultCode.Success; - } - - private uint ListAudioInsImpl(IVirtualMemoryManager memory, long bufferPosition, long bufferSize, bool filtered = false) - { - uint count = 0; - - MemoryHelper.FillWithZeros(memory, bufferPosition, (int)bufferSize); - - if (bufferSize > 0) - { - // NOTE: The service also check that the input target is enabled when in filtering mode, as audctl and most of the audin logic isn't supported, we don't support it. - if (!filtered) - { - byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioInsName + "\0"); - - memory.Write((ulong)bufferPosition, deviceNameBuffer); - - count++; - } - - // NOTE: The service adds other input devices names available in the buffer, - // every name is aligned to 0x100 bytes. - // Since we don't support it for now, it's fine to do nothing here. - } - - return count; - } + 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/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs index 6204a7be..0b164019 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/IAudioOutManager.cs @@ -1,147 +1,12 @@ -using Ryujinx.Audio; -using Ryujinx.Common.Logging; -using Ryujinx.Cpu; -using Ryujinx.HLE.HOS.Kernel.Threading; -using Ryujinx.HLE.HOS.Services.Audio.AudioOutManager; -using System.Text; +using Ryujinx.Audio.Common; +using Ryujinx.HLE.HOS.Services.Audio.AudioOut; namespace Ryujinx.HLE.HOS.Services.Audio { - [Service("audout:u")] - class IAudioOutManager : IpcService + interface IAudioOutManager { - private const string DefaultAudioOutput = "DeviceOut"; - private const int DefaultSampleRate = 48000; - private const int DefaultChannelsCount = 2; + public string[] ListAudioOuts(); - public IAudioOutManager(ServiceCtx context) : base(context.Device.System.AudOutServer) { } - - [Command(0)] - // ListAudioOuts() -> (u32 count, buffer<bytes, 6>) - public ResultCode ListAudioOuts(ServiceCtx context) - { - return ListAudioOutsImpl(context, context.Request.ReceiveBuff[0].Position, context.Request.ReceiveBuff[0].Size); - } - - [Command(1)] - // OpenAudioOut(u32 sample_rate, u16 unused, u16 channel_count, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 5> name_in) - // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioOut>, buffer<bytes, 6> name_out) - public ResultCode OpenAudioOut(ServiceCtx context) - { - return OpenAudioOutImpl(context, context.Request.SendBuff[0].Position, context.Request.SendBuff[0].Size, - context.Request.ReceiveBuff[0].Position, context.Request.ReceiveBuff[0].Size); - } - - [Command(2)] // 3.0.0+ - // ListAudioOutsAuto() -> (u32 count, buffer<bytes, 0x22>) - public ResultCode ListAudioOutsAuto(ServiceCtx context) - { - (long recvPosition, long recvSize) = context.Request.GetBufferType0x22(); - - return ListAudioOutsImpl(context, recvPosition, recvSize); - } - - [Command(3)] // 3.0.0+ - // OpenAudioOutAuto(u32 sample_rate, u16 unused, u16 channel_count, nn::applet::AppletResourceUserId, pid, handle<copy, process>, buffer<bytes, 0x21>) - // -> (u32 sample_rate, u32 channel_count, u32 pcm_format, u32, object<nn::audio::detail::IAudioOut>, buffer<bytes, 0x22> name_out) - public ResultCode OpenAudioOutAuto(ServiceCtx context) - { - (long sendPosition, long sendSize) = context.Request.GetBufferType0x21(); - (long recvPosition, long recvSize) = context.Request.GetBufferType0x22(); - - return OpenAudioOutImpl(context, sendPosition, sendSize, recvPosition, recvSize); - } - - private ResultCode ListAudioOutsImpl(ServiceCtx context, long position, long size) - { - int nameCount = 0; - - byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(DefaultAudioOutput + "\0"); - - if ((ulong)deviceNameBuffer.Length <= (ulong)size) - { - context.Memory.Write((ulong)position, deviceNameBuffer); - - nameCount++; - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {size} too small!"); - } - - context.ResponseData.Write(nameCount); - - return ResultCode.Success; - } - - private ResultCode OpenAudioOutImpl(ServiceCtx context, long sendPosition, long sendSize, long receivePosition, long receiveSize) - { - string deviceName = MemoryHelper.ReadAsciiString(context.Memory, sendPosition, sendSize); - - if (deviceName == string.Empty) - { - deviceName = DefaultAudioOutput; - } - - if (deviceName != DefaultAudioOutput) - { - Logger.Warning?.Print(LogClass.Audio, "Invalid device name!"); - - return ResultCode.DeviceNotFound; - } - - byte[] deviceNameBuffer = Encoding.ASCII.GetBytes(deviceName + "\0"); - - if ((ulong)deviceNameBuffer.Length <= (ulong)receiveSize) - { - context.Memory.Write((ulong)receivePosition, deviceNameBuffer); - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Output buffer size {receiveSize} too small!"); - } - - int sampleRate = context.RequestData.ReadInt32(); - int channels = context.RequestData.ReadInt32(); - - if (sampleRate == 0) - { - sampleRate = DefaultSampleRate; - } - - if (sampleRate != DefaultSampleRate) - { - Logger.Warning?.Print(LogClass.Audio, "Invalid sample rate!"); - - return ResultCode.UnsupportedSampleRate; - } - - channels = (ushort)channels; - - if (channels == 0) - { - channels = DefaultChannelsCount; - } - - KEvent releaseEvent = new KEvent(context.Device.System.KernelContext); - - ReleaseCallback callback = () => - { - releaseEvent.ReadableEvent.Signal(); - }; - - IAalOutput audioOut = context.Device.AudioOut; - - int track = audioOut.OpenTrack(sampleRate, channels, callback); - - MakeObject(context, new IAudioOut(context.Device.System.KernelContext, audioOut, releaseEvent, track, context.Request.HandleDesc.ToCopy[0])); - - context.ResponseData.Write(sampleRate); - context.ResponseData.Write(channels); - context.ResponseData.Write((int)SampleFormat.PcmInt16); - context.ResponseData.Write((int)PlaybackState.Stopped); - - return ResultCode.Success; - } + public ResultCode OpenAudioOut(ServiceCtx context, out string outputDeviceName, out AudioOutputConfiguration outputConfiguration, out IAudioOut obj, string inputDeviceName, ref AudioInputConfiguration parameter, ulong appletResourceUserId, uint processHandle); } -}
\ No newline at end of file +} diff --git a/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs b/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs deleted file mode 100644 index 654436e4..00000000 --- a/Ryujinx.HLE/HOS/Services/Audio/Types/SampleFormat.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Ryujinx.HLE.HOS.Services.Audio -{ - enum SampleFormat : byte - { - Invalid = 0, - PcmInt8 = 1, - PcmInt16 = 2, - PcmInt24 = 3, - PcmInt32 = 4, - PcmFloat = 5, - Adpcm = 6 - } -}
\ No newline at end of file diff --git a/Ryujinx.HLE/Ryujinx.HLE.csproj b/Ryujinx.HLE/Ryujinx.HLE.csproj index c3b5ac7a..3d48d893 100644 --- a/Ryujinx.HLE/Ryujinx.HLE.csproj +++ b/Ryujinx.HLE/Ryujinx.HLE.csproj @@ -6,7 +6,6 @@ </PropertyGroup> <ItemGroup> - <ProjectReference Include="..\Ryujinx.Audio.Renderer\Ryujinx.Audio.Renderer.csproj" /> <ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" /> <ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" /> <ProjectReference Include="..\Ryujinx.Cpu\Ryujinx.Cpu.csproj" /> diff --git a/Ryujinx.HLE/Switch.cs b/Ryujinx.HLE/Switch.cs index 2306e5d3..865a86d7 100644 --- a/Ryujinx.HLE/Switch.cs +++ b/Ryujinx.HLE/Switch.cs @@ -1,5 +1,6 @@ using LibHac.FsSystem; -using Ryujinx.Audio; +using Ryujinx.Audio.Backends.CompatLayer; +using Ryujinx.Audio.Integration; using Ryujinx.Common; using Ryujinx.Configuration; using Ryujinx.Graphics.GAL; @@ -22,7 +23,7 @@ namespace Ryujinx.HLE { public class Switch : IDisposable { - public IAalOutput AudioOut { get; private set; } + public IHardwareDeviceDriver AudioDeviceDriver { get; private set; } internal MemoryBlock Memory { get; private set; } @@ -48,16 +49,16 @@ namespace Ryujinx.HLE public bool EnableDeviceVsync { get; set; } = true; - public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, UserChannelPersistence userChannelPersistence, IRenderer renderer, IAalOutput audioOut) + public Switch(VirtualFileSystem fileSystem, ContentManager contentManager, UserChannelPersistence userChannelPersistence, IRenderer renderer, IHardwareDeviceDriver audioDeviceDriver) { if (renderer == null) { throw new ArgumentNullException(nameof(renderer)); } - if (audioOut == null) + if (audioDeviceDriver == null) { - throw new ArgumentNullException(nameof(audioOut)); + throw new ArgumentNullException(nameof(audioDeviceDriver)); } if (userChannelPersistence == null) @@ -67,7 +68,7 @@ namespace Ryujinx.HLE UserChannelPersistence = userChannelPersistence; - AudioOut = audioOut; + AudioDeviceDriver = new CompatLayerHardwareDeviceDriver(audioDeviceDriver); Memory = new MemoryBlock(1UL << 32); @@ -210,7 +211,7 @@ namespace Ryujinx.HLE System.Dispose(); Host1x.Dispose(); - AudioOut.Dispose(); + AudioDeviceDriver.Dispose(); FileSystem.Unload(); Memory.Dispose(); } |