Move solution and projects to src
This commit is contained in:
178
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs
Normal file
178
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIo.cs
Normal file
@ -0,0 +1,178 @@
|
||||
using Ryujinx.Common.Memory;
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public static partial class SoundIo
|
||||
{
|
||||
private const string LibraryName = "libsoundio";
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void OnDeviceChangeNativeDelegate(IntPtr ctx);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void OnBackendDisconnectedDelegate(IntPtr ctx, SoundIoError err);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void OnEventsSignalDelegate(IntPtr ctx);
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void EmitRtPrioWarningDelegate();
|
||||
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
public unsafe delegate void JackCallbackDelegate(IntPtr msg);
|
||||
|
||||
[StructLayout(LayoutKind.Sequential)]
|
||||
public struct SoundIoStruct
|
||||
{
|
||||
public IntPtr UserData;
|
||||
public IntPtr OnDeviceChange;
|
||||
public IntPtr OnBackendDisconnected;
|
||||
public IntPtr OnEventsSignal;
|
||||
public SoundIoBackend CurrentBackend;
|
||||
public IntPtr ApplicationName;
|
||||
public IntPtr EmitRtPrioWarning;
|
||||
public IntPtr JackInfoCallback;
|
||||
public IntPtr JackErrorCallback;
|
||||
}
|
||||
|
||||
public struct SoundIoChannelLayout
|
||||
{
|
||||
public IntPtr Name;
|
||||
public int ChannelCount;
|
||||
public Array24<SoundIoChannelId> Channels;
|
||||
|
||||
public static IntPtr GetDefault(int channelCount)
|
||||
{
|
||||
return soundio_channel_layout_get_default(channelCount);
|
||||
}
|
||||
|
||||
public static unsafe SoundIoChannelLayout GetDefaultValue(int channelCount)
|
||||
{
|
||||
return Unsafe.AsRef<SoundIoChannelLayout>((SoundIoChannelLayout*)GetDefault(channelCount));
|
||||
}
|
||||
}
|
||||
|
||||
public struct SoundIoSampleRateRange
|
||||
{
|
||||
public int Min;
|
||||
public int Max;
|
||||
}
|
||||
|
||||
public struct SoundIoDevice
|
||||
{
|
||||
public IntPtr SoundIo;
|
||||
public IntPtr Id;
|
||||
public IntPtr Name;
|
||||
public SoundIoDeviceAim Aim;
|
||||
public IntPtr Layouts;
|
||||
public int LayoutCount;
|
||||
public SoundIoChannelLayout CurrentLayout;
|
||||
public IntPtr Formats;
|
||||
public int FormatCount;
|
||||
public SoundIoFormat CurrentFormat;
|
||||
public IntPtr SampleRates;
|
||||
public int SampleRateCount;
|
||||
public int SampleRateCurrent;
|
||||
public double SoftwareLatencyMin;
|
||||
public double SoftwareLatencyMax;
|
||||
public double SoftwareLatencyCurrent;
|
||||
public bool IsRaw;
|
||||
public int RefCount;
|
||||
public SoundIoError ProbeError;
|
||||
}
|
||||
|
||||
public struct SoundIoOutStream
|
||||
{
|
||||
public IntPtr Device;
|
||||
public SoundIoFormat Format;
|
||||
public int SampleRate;
|
||||
public SoundIoChannelLayout Layout;
|
||||
public double SoftwareLatency;
|
||||
public float Volume;
|
||||
public IntPtr UserData;
|
||||
public IntPtr WriteCallback;
|
||||
public IntPtr UnderflowCallback;
|
||||
public IntPtr ErrorCallback;
|
||||
public IntPtr Name;
|
||||
public bool NonTerminalHint;
|
||||
public int BytesPerFrame;
|
||||
public int BytesPerSample;
|
||||
public SoundIoError LayoutError;
|
||||
}
|
||||
|
||||
public struct SoundIoChannelArea
|
||||
{
|
||||
public IntPtr Pointer;
|
||||
public int Step;
|
||||
}
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial IntPtr soundio_create();
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial SoundIoError soundio_connect(IntPtr ctx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial void soundio_disconnect(IntPtr ctx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial void soundio_flush_events(IntPtr ctx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial int soundio_output_device_count(IntPtr ctx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial int soundio_default_output_device_index(IntPtr ctx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial IntPtr soundio_get_output_device(IntPtr ctx, int index);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool soundio_device_supports_format(IntPtr devCtx, SoundIoFormat format);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool soundio_device_supports_layout(IntPtr devCtx, IntPtr layout);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
[return: MarshalAs(UnmanagedType.Bool)]
|
||||
public static partial bool soundio_device_supports_sample_rate(IntPtr devCtx, int sampleRate);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial IntPtr soundio_outstream_create(IntPtr devCtx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial SoundIoError soundio_outstream_open(IntPtr outStreamCtx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial SoundIoError soundio_outstream_start(IntPtr outStreamCtx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial SoundIoError soundio_outstream_begin_write(IntPtr outStreamCtx, IntPtr areas, IntPtr frameCount);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial SoundIoError soundio_outstream_end_write(IntPtr outStreamCtx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial SoundIoError soundio_outstream_pause(IntPtr devCtx, [MarshalAs(UnmanagedType.Bool)] bool pause);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial SoundIoError soundio_outstream_set_volume(IntPtr devCtx, double volume);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial void soundio_outstream_destroy(IntPtr streamCtx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial void soundio_destroy(IntPtr ctx);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial IntPtr soundio_channel_layout_get_default(int channelCount);
|
||||
|
||||
[LibraryImport(LibraryName)]
|
||||
public static partial IntPtr soundio_strerror(SoundIoError err);
|
||||
}
|
||||
}
|
13
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs
Normal file
13
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoBackend.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public enum SoundIoBackend : int
|
||||
{
|
||||
None = 0,
|
||||
Jack = 1,
|
||||
PulseAudio = 2,
|
||||
Alsa = 3,
|
||||
CoreAudio = 4,
|
||||
Wasapi = 5,
|
||||
Dummy = 6
|
||||
}
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public enum SoundIoChannelId
|
||||
{
|
||||
Invalid = 0,
|
||||
FrontLeft = 1,
|
||||
FrontRight = 2,
|
||||
FrontCenter = 3,
|
||||
Lfe = 4,
|
||||
BackLeft = 5,
|
||||
BackRight = 6,
|
||||
FrontLeftCenter = 7,
|
||||
FrontRightCenter = 8,
|
||||
BackCenter = 9,
|
||||
SideLeft = 10,
|
||||
SideRight = 11,
|
||||
TopCenter = 12,
|
||||
TopFrontLeft = 13,
|
||||
TopFrontCenter = 14,
|
||||
TopFrontRight = 15,
|
||||
TopBackLeft = 16,
|
||||
TopBackCenter = 17,
|
||||
TopBackRight = 18,
|
||||
BackLeftCenter = 19,
|
||||
BackRightCenter = 20,
|
||||
FrontLeftWide = 21,
|
||||
FrontRightWide = 22,
|
||||
FrontLeftHigh = 23,
|
||||
FrontCenterHigh = 24,
|
||||
FrontRightHigh = 25,
|
||||
TopFrontLeftCenter = 26,
|
||||
TopFrontRightCenter = 27,
|
||||
TopSideLeft = 28,
|
||||
TopSideRight = 29,
|
||||
LeftLfe = 30,
|
||||
RightLfe = 31,
|
||||
Lfe2 = 32,
|
||||
BottomCenter = 33,
|
||||
BottomLeftCenter = 34,
|
||||
BottomRightCenter = 35,
|
||||
MsMid = 36,
|
||||
MsSide = 37,
|
||||
AmbisonicW = 38,
|
||||
AmbisonicX = 39,
|
||||
AmbisonicY = 40,
|
||||
AmbisonicZ = 41,
|
||||
XyX = 42,
|
||||
XyY = 43,
|
||||
HeadphonesLeft = 44,
|
||||
HeadphonesRight = 45,
|
||||
ClickTrack = 46,
|
||||
ForeignLanguage = 47,
|
||||
HearingImpaired = 48,
|
||||
Narration = 49,
|
||||
Haptic = 50,
|
||||
DialogCentricMix = 51,
|
||||
Aux = 52,
|
||||
Aux0 = 53,
|
||||
Aux1 = 54,
|
||||
Aux2 = 55,
|
||||
Aux3 = 56,
|
||||
Aux4 = 57,
|
||||
Aux5 = 58,
|
||||
Aux6 = 59,
|
||||
Aux7 = 60,
|
||||
Aux8 = 61,
|
||||
Aux9 = 62,
|
||||
Aux10 = 63,
|
||||
Aux11 = 64,
|
||||
Aux12 = 65,
|
||||
Aux13 = 66,
|
||||
Aux14 = 67,
|
||||
Aux15 = 68,
|
||||
}
|
||||
}
|
107
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs
Normal file
107
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoContext.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Reflection.Metadata;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public class SoundIoContext : IDisposable
|
||||
{
|
||||
private IntPtr _context;
|
||||
private Action<SoundIoError> _onBackendDisconnect;
|
||||
private OnBackendDisconnectedDelegate _onBackendDisconnectNative;
|
||||
|
||||
public IntPtr Context => _context;
|
||||
|
||||
internal SoundIoContext(IntPtr context)
|
||||
{
|
||||
_context = context;
|
||||
_onBackendDisconnect = null;
|
||||
_onBackendDisconnectNative = null;
|
||||
}
|
||||
|
||||
public SoundIoError Connect() => soundio_connect(_context);
|
||||
public void Disconnect() => soundio_disconnect(_context);
|
||||
|
||||
public void FlushEvents() => soundio_flush_events(_context);
|
||||
|
||||
public int OutputDeviceCount => soundio_output_device_count(_context);
|
||||
|
||||
public int DefaultOutputDeviceIndex => soundio_default_output_device_index(_context);
|
||||
|
||||
public Action<SoundIoError> OnBackendDisconnect
|
||||
{
|
||||
get { return _onBackendDisconnect; }
|
||||
set
|
||||
{
|
||||
_onBackendDisconnect = value;
|
||||
|
||||
if (_onBackendDisconnect == null)
|
||||
{
|
||||
_onBackendDisconnectNative = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_onBackendDisconnectNative = (ctx, err) => _onBackendDisconnect(err);
|
||||
}
|
||||
|
||||
GetContext().OnBackendDisconnected = Marshal.GetFunctionPointerForDelegate(_onBackendDisconnectNative);
|
||||
}
|
||||
}
|
||||
|
||||
private ref SoundIoStruct GetContext()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<SoundIoStruct>((SoundIoStruct*)_context);
|
||||
}
|
||||
}
|
||||
|
||||
public SoundIoDeviceContext GetOutputDevice(int index)
|
||||
{
|
||||
IntPtr deviceContext = soundio_get_output_device(_context, index);
|
||||
|
||||
if (deviceContext == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SoundIoDeviceContext(deviceContext);
|
||||
}
|
||||
|
||||
public static SoundIoContext Create()
|
||||
{
|
||||
IntPtr context = soundio_create();
|
||||
|
||||
if (context == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SoundIoContext(context);
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
IntPtr currentContext = Interlocked.Exchange(ref _context, IntPtr.Zero);
|
||||
|
||||
if (currentContext != IntPtr.Zero)
|
||||
{
|
||||
soundio_destroy(currentContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~SoundIoContext()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public enum SoundIoDeviceAim
|
||||
{
|
||||
SoundIoDeviceAimInput = 0,
|
||||
SoundIoDeviceAimOutput = 1
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public class SoundIoDeviceContext
|
||||
{
|
||||
private readonly IntPtr _context;
|
||||
|
||||
public IntPtr Context => _context;
|
||||
|
||||
internal SoundIoDeviceContext(IntPtr context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
private ref SoundIoDevice GetDeviceContext()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<SoundIoDevice>((SoundIoDevice*)_context);
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsRaw => GetDeviceContext().IsRaw;
|
||||
|
||||
public string Id => Marshal.PtrToStringAnsi(GetDeviceContext().Id);
|
||||
|
||||
public bool SupportsSampleRate(int sampleRate) => soundio_device_supports_sample_rate(_context, sampleRate);
|
||||
|
||||
public bool SupportsFormat(SoundIoFormat format) => soundio_device_supports_format(_context, format);
|
||||
|
||||
public bool SupportsChannelCount(int channelCount) => soundio_device_supports_layout(_context, SoundIoChannelLayout.GetDefault(channelCount));
|
||||
|
||||
public SoundIoOutStreamContext CreateOutStream()
|
||||
{
|
||||
IntPtr context = soundio_outstream_create(_context);
|
||||
|
||||
if (context == IntPtr.Zero)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return new SoundIoOutStreamContext(context);
|
||||
}
|
||||
}
|
||||
}
|
22
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs
Normal file
22
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoError.cs
Normal file
@ -0,0 +1,22 @@
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public enum SoundIoError
|
||||
{
|
||||
None = 0,
|
||||
NoMem = 1,
|
||||
InitAudioBackend = 2,
|
||||
SystemResources = 3,
|
||||
OpeningDevice = 4,
|
||||
NoSuchDevice = 5,
|
||||
Invalid = 6,
|
||||
BackendUnavailable = 7,
|
||||
Streaming = 8,
|
||||
IncompatibleDevice = 9,
|
||||
NoSuchClient = 10,
|
||||
IncompatibleBackend = 11,
|
||||
BackendDisconnected = 12,
|
||||
Interrupted = 13,
|
||||
Underflow = 14,
|
||||
EncodingString = 15,
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
internal class SoundIoException : Exception
|
||||
{
|
||||
internal SoundIoException(SoundIoError error) : base(Marshal.PtrToStringAnsi(soundio_strerror(error))) { }
|
||||
}
|
||||
}
|
25
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs
Normal file
25
src/Ryujinx.Audio.Backends.SoundIo/Native/SoundIoFormat.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public enum SoundIoFormat
|
||||
{
|
||||
Invalid = 0,
|
||||
S8 = 1,
|
||||
U8 = 2,
|
||||
S16LE = 3,
|
||||
S16BE = 4,
|
||||
U16LE = 5,
|
||||
U16BE = 6,
|
||||
S24LE = 7,
|
||||
S24BE = 8,
|
||||
U24LE = 9,
|
||||
U24BE = 10,
|
||||
S32LE = 11,
|
||||
S32BE = 12,
|
||||
U32LE = 13,
|
||||
U32BE = 14,
|
||||
Float32LE = 15,
|
||||
Float32BE = 16,
|
||||
Float64LE = 17,
|
||||
Float64BE = 18,
|
||||
}
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo.Native
|
||||
{
|
||||
public class SoundIoOutStreamContext : IDisposable
|
||||
{
|
||||
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||
private unsafe delegate void WriteCallbackDelegate(IntPtr ctx, int frameCountMin, int frameCountMax);
|
||||
|
||||
private IntPtr _context;
|
||||
private IntPtr _nameStored;
|
||||
private Action<int, int> _writeCallback;
|
||||
private WriteCallbackDelegate _writeCallbackNative;
|
||||
|
||||
public IntPtr Context => _context;
|
||||
|
||||
internal SoundIoOutStreamContext(IntPtr context)
|
||||
{
|
||||
_context = context;
|
||||
_nameStored = IntPtr.Zero;
|
||||
_writeCallback = null;
|
||||
_writeCallbackNative = null;
|
||||
}
|
||||
|
||||
private ref SoundIoOutStream GetOutContext()
|
||||
{
|
||||
unsafe
|
||||
{
|
||||
return ref Unsafe.AsRef<SoundIoOutStream>((SoundIoOutStream*)_context);
|
||||
}
|
||||
}
|
||||
|
||||
public string Name
|
||||
{
|
||||
get => Marshal.PtrToStringAnsi(GetOutContext().Name);
|
||||
set
|
||||
{
|
||||
var context = GetOutContext();
|
||||
|
||||
if (_nameStored != IntPtr.Zero && context.Name == _nameStored)
|
||||
{
|
||||
Marshal.FreeHGlobal(_nameStored);
|
||||
}
|
||||
|
||||
_nameStored = Marshal.StringToHGlobalAnsi(value);
|
||||
GetOutContext().Name = _nameStored;
|
||||
}
|
||||
}
|
||||
|
||||
public SoundIoChannelLayout Layout
|
||||
{
|
||||
get => GetOutContext().Layout;
|
||||
set => GetOutContext().Layout = value;
|
||||
}
|
||||
|
||||
public SoundIoFormat Format
|
||||
{
|
||||
get => GetOutContext().Format;
|
||||
set => GetOutContext().Format = value;
|
||||
}
|
||||
|
||||
public int SampleRate
|
||||
{
|
||||
get => GetOutContext().SampleRate;
|
||||
set => GetOutContext().SampleRate = value;
|
||||
}
|
||||
|
||||
public float Volume
|
||||
{
|
||||
get => GetOutContext().Volume;
|
||||
set => GetOutContext().Volume = value;
|
||||
}
|
||||
|
||||
public int BytesPerFrame
|
||||
{
|
||||
get => GetOutContext().BytesPerFrame;
|
||||
set => GetOutContext().BytesPerFrame = value;
|
||||
}
|
||||
|
||||
public int BytesPerSample
|
||||
{
|
||||
get => GetOutContext().BytesPerSample;
|
||||
set => GetOutContext().BytesPerSample = value;
|
||||
}
|
||||
|
||||
public Action<int, int> WriteCallback
|
||||
{
|
||||
get { return _writeCallback; }
|
||||
set
|
||||
{
|
||||
_writeCallback = value;
|
||||
|
||||
if (_writeCallback == null)
|
||||
{
|
||||
_writeCallbackNative = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_writeCallbackNative = (ctx, frameCountMin, frameCountMax) => _writeCallback(frameCountMin, frameCountMax);
|
||||
}
|
||||
|
||||
GetOutContext().WriteCallback = Marshal.GetFunctionPointerForDelegate(_writeCallbackNative);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckError(SoundIoError error)
|
||||
{
|
||||
if (error != SoundIoError.None)
|
||||
{
|
||||
throw new SoundIoException(error);
|
||||
}
|
||||
}
|
||||
|
||||
public void Open() => CheckError(soundio_outstream_open(_context));
|
||||
|
||||
public void Start() => CheckError(soundio_outstream_start(_context));
|
||||
|
||||
public void Pause(bool pause) => CheckError(soundio_outstream_pause(_context, pause));
|
||||
|
||||
public void SetVolume(double volume) => CheckError(soundio_outstream_set_volume(_context, volume));
|
||||
|
||||
public Span<SoundIoChannelArea> BeginWrite(ref int frameCount)
|
||||
{
|
||||
IntPtr arenas = default;
|
||||
int nativeFrameCount = frameCount;
|
||||
|
||||
unsafe
|
||||
{
|
||||
var frameCountPtr = &nativeFrameCount;
|
||||
var arenasPtr = &arenas;
|
||||
CheckError(soundio_outstream_begin_write(_context, (IntPtr)arenasPtr, (IntPtr)frameCountPtr));
|
||||
|
||||
frameCount = *frameCountPtr;
|
||||
|
||||
return new Span<SoundIoChannelArea>((void*)arenas, Layout.ChannelCount);
|
||||
}
|
||||
}
|
||||
|
||||
public void EndWrite() => CheckError(soundio_outstream_end_write(_context));
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (_context != IntPtr.Zero)
|
||||
{
|
||||
soundio_outstream_destroy(_context);
|
||||
_context = IntPtr.Zero;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Dispose(true);
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
~SoundIoOutStreamContext()
|
||||
{
|
||||
Dispose(false);
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,28 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<RuntimeIdentifiers>win10-x64;linux-x64;osx-x64</RuntimeIdentifiers>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Audio\Ryujinx.Audio.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dll" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dll</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.dylib" Condition="'$(RuntimeIdentifier)' != 'linux-x64' AND '$(RuntimeIdentifier)' != 'win10-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.dylib</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
<ContentWithTargetPath Include="Native\libsoundio\libs\libsoundio.so" Condition="'$(RuntimeIdentifier)' != 'win10-x64' AND '$(RuntimeIdentifier)' != 'osx-x64'">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
<TargetPath>libsoundio.so</TargetPath>
|
||||
</ContentWithTargetPath>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
16
src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs
Normal file
16
src/Ryujinx.Audio.Backends.SoundIo/SoundIoAudioBuffer.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Ryujinx.Audio.Backends.SoundIo
|
||||
{
|
||||
class SoundIoAudioBuffer
|
||||
{
|
||||
public readonly ulong DriverIdentifier;
|
||||
public readonly ulong SampleCount;
|
||||
public ulong SamplePlayed;
|
||||
|
||||
public SoundIoAudioBuffer(ulong driverIdentifier, ulong sampleCount)
|
||||
{
|
||||
DriverIdentifier = driverIdentifier;
|
||||
SampleCount = sampleCount;
|
||||
SamplePlayed = 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,247 @@
|
||||
using Ryujinx.Audio.Backends.SoundIo.Native;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Audio.Integration;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
using static Ryujinx.Audio.Integration.IHardwareDeviceDriver;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo
|
||||
{
|
||||
public class SoundIoHardwareDeviceDriver : IHardwareDeviceDriver
|
||||
{
|
||||
private readonly SoundIoContext _audioContext;
|
||||
private readonly SoundIoDeviceContext _audioDevice;
|
||||
private readonly ManualResetEvent _updateRequiredEvent;
|
||||
private readonly ManualResetEvent _pauseEvent;
|
||||
private readonly ConcurrentDictionary<SoundIoHardwareDeviceSession, byte> _sessions;
|
||||
private int _disposeState;
|
||||
|
||||
public SoundIoHardwareDeviceDriver()
|
||||
{
|
||||
_audioContext = SoundIoContext.Create();
|
||||
_updateRequiredEvent = new ManualResetEvent(false);
|
||||
_pauseEvent = new ManualResetEvent(true);
|
||||
_sessions = new ConcurrentDictionary<SoundIoHardwareDeviceSession, byte>();
|
||||
|
||||
_audioContext.Connect();
|
||||
_audioContext.FlushEvents();
|
||||
|
||||
_audioDevice = FindValidAudioDevice(_audioContext, true);
|
||||
}
|
||||
|
||||
public static bool IsSupported => IsSupportedInternal();
|
||||
|
||||
private static bool IsSupportedInternal()
|
||||
{
|
||||
SoundIoContext context = null;
|
||||
SoundIoDeviceContext device = null;
|
||||
SoundIoOutStreamContext stream = null;
|
||||
|
||||
bool backendDisconnected = false;
|
||||
|
||||
try
|
||||
{
|
||||
context = SoundIoContext.Create();
|
||||
context.OnBackendDisconnect = err =>
|
||||
{
|
||||
backendDisconnected = true;
|
||||
};
|
||||
|
||||
context.Connect();
|
||||
context.FlushEvents();
|
||||
|
||||
if (backendDisconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (context.OutputDeviceCount == 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
device = FindValidAudioDevice(context);
|
||||
|
||||
if (device == null || backendDisconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
stream = device.CreateOutStream();
|
||||
|
||||
if (stream == null || backendDisconnected)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
stream?.Dispose();
|
||||
context?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private static SoundIoDeviceContext FindValidAudioDevice(SoundIoContext audioContext, bool fallback = false)
|
||||
{
|
||||
SoundIoDeviceContext defaultAudioDevice = audioContext.GetOutputDevice(audioContext.DefaultOutputDeviceIndex);
|
||||
|
||||
if (!defaultAudioDevice.IsRaw)
|
||||
{
|
||||
return defaultAudioDevice;
|
||||
}
|
||||
|
||||
for (int i = 0; i < audioContext.OutputDeviceCount; i++)
|
||||
{
|
||||
SoundIoDeviceContext audioDevice = audioContext.GetOutputDevice(i);
|
||||
|
||||
if (audioDevice.Id == defaultAudioDevice.Id && !audioDevice.IsRaw)
|
||||
{
|
||||
return audioDevice;
|
||||
}
|
||||
}
|
||||
|
||||
return fallback ? defaultAudioDevice : null;
|
||||
}
|
||||
|
||||
public ManualResetEvent GetUpdateRequiredEvent()
|
||||
{
|
||||
return _updateRequiredEvent;
|
||||
}
|
||||
|
||||
public ManualResetEvent GetPauseEvent()
|
||||
{
|
||||
return _pauseEvent;
|
||||
}
|
||||
|
||||
public IHardwareDeviceSession OpenDeviceSession(Direction direction, IVirtualMemoryManager memoryManager, SampleFormat sampleFormat, uint sampleRate, uint channelCount, float volume)
|
||||
{
|
||||
if (channelCount == 0)
|
||||
{
|
||||
channelCount = 2;
|
||||
}
|
||||
|
||||
if (sampleRate == 0)
|
||||
{
|
||||
sampleRate = Constants.TargetSampleRate;
|
||||
}
|
||||
|
||||
volume = Math.Clamp(volume, 0, 1);
|
||||
|
||||
if (direction != Direction.Output)
|
||||
{
|
||||
throw new NotImplementedException("Input direction is currently not implemented on SoundIO backend!");
|
||||
}
|
||||
|
||||
SoundIoHardwareDeviceSession session = new SoundIoHardwareDeviceSession(this, memoryManager, sampleFormat, sampleRate, channelCount, volume);
|
||||
|
||||
_sessions.TryAdd(session, 0);
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
internal bool Unregister(SoundIoHardwareDeviceSession session)
|
||||
{
|
||||
return _sessions.TryRemove(session, out _);
|
||||
}
|
||||
|
||||
public static SoundIoFormat GetSoundIoFormat(SampleFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
SampleFormat.PcmInt8 => SoundIoFormat.S8,
|
||||
SampleFormat.PcmInt16 => SoundIoFormat.S16LE,
|
||||
SampleFormat.PcmInt24 => SoundIoFormat.S24LE,
|
||||
SampleFormat.PcmInt32 => SoundIoFormat.S32LE,
|
||||
SampleFormat.PcmFloat => SoundIoFormat.Float32LE,
|
||||
_ => throw new ArgumentException ($"Unsupported sample format {format}"),
|
||||
};
|
||||
}
|
||||
|
||||
internal SoundIoOutStreamContext OpenStream(SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount)
|
||||
{
|
||||
SoundIoFormat driverSampleFormat = GetSoundIoFormat(requestedSampleFormat);
|
||||
|
||||
if (!_audioDevice.SupportsSampleRate((int)requestedSampleRate))
|
||||
{
|
||||
throw new ArgumentException($"This sound device does not support a sample rate of {requestedSampleRate}Hz");
|
||||
}
|
||||
|
||||
if (!_audioDevice.SupportsFormat(driverSampleFormat))
|
||||
{
|
||||
throw new ArgumentException($"This sound device does not support {requestedSampleFormat}");
|
||||
}
|
||||
|
||||
if (!_audioDevice.SupportsChannelCount((int)requestedChannelCount))
|
||||
{
|
||||
throw new ArgumentException($"This sound device does not support channel count {requestedChannelCount}");
|
||||
}
|
||||
|
||||
SoundIoOutStreamContext result = _audioDevice.CreateOutStream();
|
||||
|
||||
result.Name = "Ryujinx";
|
||||
result.Layout = SoundIoChannelLayout.GetDefaultValue((int)requestedChannelCount);
|
||||
result.Format = driverSampleFormat;
|
||||
result.SampleRate = (int)requestedSampleRate;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
internal void FlushContextEvents()
|
||||
{
|
||||
_audioContext.FlushEvents();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing)
|
||||
{
|
||||
foreach (SoundIoHardwareDeviceSession session in _sessions.Keys)
|
||||
{
|
||||
session.Dispose();
|
||||
}
|
||||
|
||||
_audioContext.Disconnect();
|
||||
_audioContext.Dispose();
|
||||
_pauseEvent.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public bool SupportsSampleRate(uint sampleRate)
|
||||
{
|
||||
return _audioDevice.SupportsSampleRate((int)sampleRate);
|
||||
}
|
||||
|
||||
public bool SupportsSampleFormat(SampleFormat sampleFormat)
|
||||
{
|
||||
return _audioDevice.SupportsFormat(GetSoundIoFormat(sampleFormat));
|
||||
}
|
||||
|
||||
public bool SupportsChannelCount(uint channelCount)
|
||||
{
|
||||
return _audioDevice.SupportsChannelCount((int)channelCount);
|
||||
}
|
||||
|
||||
public bool SupportsDirection(Direction direction)
|
||||
{
|
||||
// TODO: add direction input when supported.
|
||||
return direction == Direction.Output;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,438 @@
|
||||
using Ryujinx.Audio.Backends.Common;
|
||||
using Ryujinx.Audio.Backends.SoundIo.Native;
|
||||
using Ryujinx.Audio.Common;
|
||||
using Ryujinx.Memory;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using static Ryujinx.Audio.Backends.SoundIo.Native.SoundIo;
|
||||
|
||||
namespace Ryujinx.Audio.Backends.SoundIo
|
||||
{
|
||||
class SoundIoHardwareDeviceSession : HardwareDeviceSessionOutputBase
|
||||
{
|
||||
private SoundIoHardwareDeviceDriver _driver;
|
||||
private ConcurrentQueue<SoundIoAudioBuffer> _queuedBuffers;
|
||||
private SoundIoOutStreamContext _outputStream;
|
||||
private DynamicRingBuffer _ringBuffer;
|
||||
private ulong _playedSampleCount;
|
||||
private ManualResetEvent _updateRequiredEvent;
|
||||
private int _disposeState;
|
||||
|
||||
public SoundIoHardwareDeviceSession(SoundIoHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount)
|
||||
{
|
||||
_driver = driver;
|
||||
_updateRequiredEvent = _driver.GetUpdateRequiredEvent();
|
||||
_queuedBuffers = new ConcurrentQueue<SoundIoAudioBuffer>();
|
||||
_ringBuffer = new DynamicRingBuffer();
|
||||
|
||||
SetupOutputStream(requestedVolume);
|
||||
}
|
||||
|
||||
private void SetupOutputStream(float requestedVolume)
|
||||
{
|
||||
_outputStream = _driver.OpenStream(RequestedSampleFormat, RequestedSampleRate, RequestedChannelCount);
|
||||
_outputStream.WriteCallback += Update;
|
||||
_outputStream.Volume = requestedVolume;
|
||||
// TODO: Setup other callbacks (errors, ect).
|
||||
|
||||
_outputStream.Open();
|
||||
}
|
||||
|
||||
public override ulong GetPlayedSampleCount()
|
||||
{
|
||||
return Interlocked.Read(ref _playedSampleCount);
|
||||
}
|
||||
|
||||
public override float GetVolume()
|
||||
{
|
||||
return _outputStream.Volume;
|
||||
}
|
||||
|
||||
public override void PrepareToClose() { }
|
||||
|
||||
public override void QueueBuffer(AudioBuffer buffer)
|
||||
{
|
||||
SoundIoAudioBuffer driverBuffer = new SoundIoAudioBuffer(buffer.DataPointer, GetSampleCount(buffer));
|
||||
|
||||
_ringBuffer.Write(buffer.Data, 0, buffer.Data.Length);
|
||||
|
||||
_queuedBuffers.Enqueue(driverBuffer);
|
||||
}
|
||||
|
||||
public override void SetVolume(float volume)
|
||||
{
|
||||
_outputStream.SetVolume(volume);
|
||||
}
|
||||
|
||||
public override void Start()
|
||||
{
|
||||
_outputStream.Start();
|
||||
_outputStream.Pause(false);
|
||||
|
||||
_driver.FlushContextEvents();
|
||||
}
|
||||
|
||||
public override void Stop()
|
||||
{
|
||||
_outputStream.Pause(true);
|
||||
|
||||
_driver.FlushContextEvents();
|
||||
}
|
||||
|
||||
public override void UnregisterBuffer(AudioBuffer buffer) {}
|
||||
|
||||
public override bool WasBufferFullyConsumed(AudioBuffer buffer)
|
||||
{
|
||||
if (!_queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return driverBuffer.DriverIdentifier != buffer.DataPointer;
|
||||
}
|
||||
|
||||
private unsafe void Update(int minFrameCount, int maxFrameCount)
|
||||
{
|
||||
int bytesPerFrame = _outputStream.BytesPerFrame;
|
||||
uint bytesPerSample = (uint)_outputStream.BytesPerSample;
|
||||
|
||||
int bufferedFrames = _ringBuffer.Length / bytesPerFrame;
|
||||
|
||||
int frameCount = Math.Min(bufferedFrames, maxFrameCount);
|
||||
|
||||
if (frameCount == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Span<SoundIoChannelArea> areas = _outputStream.BeginWrite(ref frameCount);
|
||||
|
||||
int channelCount = areas.Length;
|
||||
|
||||
byte[] samples = new byte[frameCount * bytesPerFrame];
|
||||
|
||||
_ringBuffer.Read(samples, 0, samples.Length);
|
||||
|
||||
// This is a huge ugly block of code, but we save
|
||||
// a significant amount of time over the generic
|
||||
// loop that handles other channel counts.
|
||||
// TODO: Is this still right in 2022?
|
||||
|
||||
// Mono
|
||||
if (channelCount == 1)
|
||||
{
|
||||
ref SoundIoChannelArea area = ref areas[0];
|
||||
|
||||
fixed (byte* srcptr = samples)
|
||||
{
|
||||
if (bytesPerSample == 1)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame];
|
||||
|
||||
area.Pointer += area.Step;
|
||||
}
|
||||
}
|
||||
else if (bytesPerSample == 2)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1];
|
||||
|
||||
area.Pointer += area.Step;
|
||||
}
|
||||
}
|
||||
else if (bytesPerSample == 4)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2];
|
||||
|
||||
area.Pointer += area.Step;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample);
|
||||
|
||||
area.Pointer += area.Step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Stereo
|
||||
else if (channelCount == 2)
|
||||
{
|
||||
ref SoundIoChannelArea area1 = ref areas[0];
|
||||
ref SoundIoChannelArea area2 = ref areas[1];
|
||||
|
||||
fixed (byte* srcptr = samples)
|
||||
{
|
||||
if (bytesPerSample == 1)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
// Channel 1
|
||||
((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0];
|
||||
|
||||
// Channel 2
|
||||
((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1];
|
||||
|
||||
area1.Pointer += area1.Step;
|
||||
area2.Pointer += area2.Step;
|
||||
}
|
||||
}
|
||||
else if (bytesPerSample == 2)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
// Channel 1
|
||||
((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0];
|
||||
|
||||
// Channel 2
|
||||
((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1];
|
||||
|
||||
area1.Pointer += area1.Step;
|
||||
area2.Pointer += area2.Step;
|
||||
}
|
||||
}
|
||||
else if (bytesPerSample == 4)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
// Channel 1
|
||||
((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0];
|
||||
|
||||
// Channel 2
|
||||
((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1];
|
||||
|
||||
area1.Pointer += area1.Step;
|
||||
area2.Pointer += area2.Step;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
// Channel 1
|
||||
Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample);
|
||||
|
||||
// Channel 2
|
||||
Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample);
|
||||
|
||||
area1.Pointer += area1.Step;
|
||||
area2.Pointer += area2.Step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Surround
|
||||
else if (channelCount == 6)
|
||||
{
|
||||
ref SoundIoChannelArea area1 = ref areas[0];
|
||||
ref SoundIoChannelArea area2 = ref areas[1];
|
||||
ref SoundIoChannelArea area3 = ref areas[2];
|
||||
ref SoundIoChannelArea area4 = ref areas[3];
|
||||
ref SoundIoChannelArea area5 = ref areas[4];
|
||||
ref SoundIoChannelArea area6 = ref areas[5];
|
||||
|
||||
fixed (byte* srcptr = samples)
|
||||
{
|
||||
if (bytesPerSample == 1)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
// Channel 1
|
||||
((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0];
|
||||
|
||||
// Channel 2
|
||||
((byte*)area2.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 1];
|
||||
|
||||
// Channel 3
|
||||
((byte*)area3.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 2];
|
||||
|
||||
// Channel 4
|
||||
((byte*)area4.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 3];
|
||||
|
||||
// Channel 5
|
||||
((byte*)area5.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 4];
|
||||
|
||||
// Channel 6
|
||||
((byte*)area6.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 5];
|
||||
|
||||
area1.Pointer += area1.Step;
|
||||
area2.Pointer += area2.Step;
|
||||
area3.Pointer += area3.Step;
|
||||
area4.Pointer += area4.Step;
|
||||
area5.Pointer += area5.Step;
|
||||
area6.Pointer += area6.Step;
|
||||
}
|
||||
}
|
||||
else if (bytesPerSample == 2)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
// Channel 1
|
||||
((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0];
|
||||
|
||||
// Channel 2
|
||||
((short*)area2.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 1];
|
||||
|
||||
// Channel 3
|
||||
((short*)area3.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 2];
|
||||
|
||||
// Channel 4
|
||||
((short*)area4.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 3];
|
||||
|
||||
// Channel 5
|
||||
((short*)area5.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 4];
|
||||
|
||||
// Channel 6
|
||||
((short*)area6.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 5];
|
||||
|
||||
area1.Pointer += area1.Step;
|
||||
area2.Pointer += area2.Step;
|
||||
area3.Pointer += area3.Step;
|
||||
area4.Pointer += area4.Step;
|
||||
area5.Pointer += area5.Step;
|
||||
area6.Pointer += area6.Step;
|
||||
}
|
||||
}
|
||||
else if (bytesPerSample == 4)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
// Channel 1
|
||||
((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0];
|
||||
|
||||
// Channel 2
|
||||
((int*)area2.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 1];
|
||||
|
||||
// Channel 3
|
||||
((int*)area3.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 2];
|
||||
|
||||
// Channel 4
|
||||
((int*)area4.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 3];
|
||||
|
||||
// Channel 5
|
||||
((int*)area5.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 4];
|
||||
|
||||
// Channel 6
|
||||
((int*)area6.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 5];
|
||||
|
||||
area1.Pointer += area1.Step;
|
||||
area2.Pointer += area2.Step;
|
||||
area3.Pointer += area3.Step;
|
||||
area4.Pointer += area4.Step;
|
||||
area5.Pointer += area5.Step;
|
||||
area6.Pointer += area6.Step;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
// Channel 1
|
||||
Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample);
|
||||
|
||||
// Channel 2
|
||||
Unsafe.CopyBlockUnaligned((byte*)area2.Pointer, srcptr + (frame * bytesPerFrame) + (1 * bytesPerSample), bytesPerSample);
|
||||
|
||||
// Channel 3
|
||||
Unsafe.CopyBlockUnaligned((byte*)area3.Pointer, srcptr + (frame * bytesPerFrame) + (2 * bytesPerSample), bytesPerSample);
|
||||
|
||||
// Channel 4
|
||||
Unsafe.CopyBlockUnaligned((byte*)area4.Pointer, srcptr + (frame * bytesPerFrame) + (3 * bytesPerSample), bytesPerSample);
|
||||
|
||||
// Channel 5
|
||||
Unsafe.CopyBlockUnaligned((byte*)area5.Pointer, srcptr + (frame * bytesPerFrame) + (4 * bytesPerSample), bytesPerSample);
|
||||
|
||||
// Channel 6
|
||||
Unsafe.CopyBlockUnaligned((byte*)area6.Pointer, srcptr + (frame * bytesPerFrame) + (5 * bytesPerSample), bytesPerSample);
|
||||
|
||||
area1.Pointer += area1.Step;
|
||||
area2.Pointer += area2.Step;
|
||||
area3.Pointer += area3.Step;
|
||||
area4.Pointer += area4.Step;
|
||||
area5.Pointer += area5.Step;
|
||||
area6.Pointer += area6.Step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Every other channel count
|
||||
else
|
||||
{
|
||||
fixed (byte* srcptr = samples)
|
||||
{
|
||||
for (int frame = 0; frame < frameCount; frame++)
|
||||
{
|
||||
for (int channel = 0; channel < areas.Length; channel++)
|
||||
{
|
||||
// Copy channel by channel, frame by frame. This is slow!
|
||||
Unsafe.CopyBlockUnaligned((byte*)areas[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample);
|
||||
|
||||
areas[channel].Pointer += areas[channel].Step;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_outputStream.EndWrite();
|
||||
|
||||
ulong sampleCount = (ulong)(samples.Length / bytesPerSample / channelCount);
|
||||
|
||||
ulong availaibleSampleCount = sampleCount;
|
||||
|
||||
bool needUpdate = false;
|
||||
|
||||
while (availaibleSampleCount > 0 && _queuedBuffers.TryPeek(out SoundIoAudioBuffer driverBuffer))
|
||||
{
|
||||
ulong sampleStillNeeded = driverBuffer.SampleCount - Interlocked.Read(ref driverBuffer.SamplePlayed);
|
||||
ulong playedAudioBufferSampleCount = Math.Min(sampleStillNeeded, availaibleSampleCount);
|
||||
|
||||
Interlocked.Add(ref driverBuffer.SamplePlayed, playedAudioBufferSampleCount);
|
||||
availaibleSampleCount -= playedAudioBufferSampleCount;
|
||||
|
||||
if (Interlocked.Read(ref driverBuffer.SamplePlayed) == driverBuffer.SampleCount)
|
||||
{
|
||||
_queuedBuffers.TryDequeue(out _);
|
||||
|
||||
needUpdate = true;
|
||||
}
|
||||
|
||||
Interlocked.Add(ref _playedSampleCount, playedAudioBufferSampleCount);
|
||||
}
|
||||
|
||||
// Notify the output if needed.
|
||||
if (needUpdate)
|
||||
{
|
||||
_updateRequiredEvent.Set();
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void Dispose(bool disposing)
|
||||
{
|
||||
if (disposing && _driver.Unregister(this))
|
||||
{
|
||||
PrepareToClose();
|
||||
Stop();
|
||||
|
||||
_outputStream.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _disposeState, 1, 0) == 0)
|
||||
{
|
||||
Dispose(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user