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
This commit is contained in:
parent
1c49089ff0
commit
f556c80d02
249 changed files with 5614 additions and 2712 deletions
|
@ -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 registerBufferEvent = new KEvent(KernelContext);
|
||||
|
||||
audioOutputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
|
||||
}
|
||||
|
||||
AudioOutputManager.Initialize(Device.AudioDeviceDriver, audioOutputRegisterBufferEvents);
|
||||
|
||||
IWritableEvent[] audioInputRegisterBufferEvents = new IWritableEvent[Constants.AudioInSessionCountMax];
|
||||
|
||||
for (int i = 0; i < audioInputRegisterBufferEvents.Length; i++)
|
||||
{
|
||||
KEvent registerBufferEvent = new KEvent(KernelContext);
|
||||
|
||||
audioInputRegisterBufferEvents[i] = new AudioKernelEvent(registerBufferEvent);
|
||||
}
|
||||
|
||||
AudioInputManager.Initialize(Device.AudioDeviceDriver, audioInputRegisterBufferEvents);
|
||||
|
||||
IWritableEvent[] systemEvents = new IWritableEvent[Constants.AudioRendererSessionCountMax];
|
||||
|
||||
for (int i = 0; i < systemEvents.Length; i++)
|
||||
{
|
||||
KEvent systemEvent = new KEvent(KernelContext);
|
||||
|
||||
writableEvents[i] = new AudioKernelEvent(systemEvent);
|
||||
systemEvents[i] = new AudioKernelEvent(systemEvent);
|
||||
}
|
||||
|
||||
HardwareDevice[] devices = new HardwareDevice[RendererConstants.AudioRendererSessionCountMax];
|
||||
AudioManager.Initialize(Device.AudioDeviceDriver.GetUpdateRequiredEvent(), AudioOutputManager.Update, AudioInputManager.Update);
|
||||
|
||||
// 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++)
|
||||
{
|
||||
devices[i] = new AalHardwareDevice(i, Device.AudioOut, 2, RendererConstants.TargetSampleRate);
|
||||
}
|
||||
AudioRendererManager.Initialize(systemEvents, Device.AudioDeviceDriver);
|
||||
|
||||
AudioRendererManager.Initialize(writableEvents, devices);
|
||||
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();
|
||||
|
|
108
Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs
Normal file
108
Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioIn.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
209
Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
Normal file
209
Ryujinx.HLE/HOS/Services/Audio/AudioIn/AudioInServer.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
34
Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs
Normal file
34
Ryujinx.HLE/HOS/Services/Audio/AudioIn/IAudioIn.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
41
Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs
Normal file
41
Ryujinx.HLE/HOS/Services/Audio/AudioInManager.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
235
Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs
Normal file
235
Ryujinx.HLE/HOS/Services/Audio/AudioInManagerServer.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
108
Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs
Normal file
108
Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOut.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
190
Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
Normal file
190
Ryujinx.HLE/HOS/Services/Audio/AudioOut/AudioOutServer.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
33
Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs
Normal file
33
Ryujinx.HLE/HOS/Services/Audio/AudioOut/IAudioOut.cs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
41
Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs
Normal file
41
Ryujinx.HLE/HOS/Services/Audio/AudioOutManager.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
162
Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs
Normal file
162
Ryujinx.HLE/HOS/Services/Audio/AudioOutManagerServer.cs
Normal file
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue