diff --git a/Ryujinx.Audio/Renderers/DummyAudioOut.cs b/Ryujinx.Audio/Renderers/DummyAudioOut.cs index dc0906008c..659734b6dc 100644 --- a/Ryujinx.Audio/Renderers/DummyAudioOut.cs +++ b/Ryujinx.Audio/Renderers/DummyAudioOut.cs @@ -30,7 +30,8 @@ namespace Ryujinx.Audio public void Stop(int trackId) { } - public void AppendBuffer(int trackID, long bufferTag, T[] buffer) where T : struct + public void AppendBuffer(int trackID, long bufferTag, T[] buffer) + where T : struct { m_Buffers.Enqueue(bufferTag); } @@ -39,10 +40,12 @@ namespace Ryujinx.Audio { List bufferTags = new List(); - for (var i = 0; i < maxCount; i++) + for (int i = 0; i < maxCount; i++) { if (!m_Buffers.TryDequeue(out long tag)) + { break; + } bufferTags.Add(tag); } diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs index 05e13a65d1..76b1290d1b 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs @@ -29,6 +29,11 @@ namespace Ryujinx.Audio /// private SoundIoAudioTrackPool m_TrackPool; + /// + /// True if SoundIO is supported on the device. + /// + public static bool IsSupported => true; + /// /// Constructs a new instance of a /// @@ -42,11 +47,6 @@ namespace Ryujinx.Audio m_AudioDevice = m_AudioContext.GetOutputDevice(m_AudioContext.DefaultOutputDeviceIndex); m_TrackPool = new SoundIoAudioTrackPool(m_AudioContext, m_AudioDevice, MaximumTracks); } - - /// - /// True if SoundIO is supported on the device. - /// - public static bool IsSupported => true; /// /// Gets the current playback state of the specified track @@ -55,7 +55,9 @@ namespace Ryujinx.Audio public PlaybackState GetState(int trackId) { if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { return track.State; + } return PlaybackState.Stopped; } @@ -69,8 +71,10 @@ namespace Ryujinx.Audio /// The created track's Track ID public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) { - if(!m_TrackPool.TryGet(out SoundIoAudioTrack track)) + if (!m_TrackPool.TryGet(out SoundIoAudioTrack track)) + { return -1; + } // Open the output. We currently only support 16-bit signed LE track.Open(sampleRate, channels, callback, SoundIOFormat.S16LE); @@ -125,7 +129,8 @@ namespace Ryujinx.Audio /// The track to append the buffer to /// The internal tag of the buffer /// The buffer to append to the track - public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct + public void AppendBuffer(int trackId, long bufferTag, T[] buffer) + where T : struct { if(m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) { @@ -141,7 +146,9 @@ namespace Ryujinx.Audio public bool ContainsBuffer(int trackId, long bufferTag) { if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { return track.ContainsBuffer(bufferTag); + } return false; } @@ -158,11 +165,8 @@ namespace Ryujinx.Audio { List bufferTags = new List(); - for(var i = 0; i < maxCount; i++) + while(maxCount-- > 0 && track.ReleasedBuffers.TryDequeue(out long tag)) { - if (!track.ReleasedBuffers.TryDequeue(out long tag)) - break; - bufferTags.Add(tag); } diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs index de354a7b23..8b0457ed9f 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs @@ -78,28 +78,36 @@ namespace Ryujinx.Audio.SoundIo /// The requested channel count of the track /// A that represents the delegate to invoke when a buffer has been released by the audio track /// The requested sample format of the track - public void Open(int sampleRate, int channelCount, ReleaseCallback callback, SoundIOFormat format = SoundIOFormat.S16LE) + public void Open( + int sampleRate, + int channelCount, + ReleaseCallback callback, + SoundIOFormat format = SoundIOFormat.S16LE) { // Close any existing audio streams if (AudioStream != null) + { Close(); + } if (!AudioDevice.SupportsSampleRate(sampleRate)) + { throw new InvalidOperationException($"This sound device does not support a sample rate of {sampleRate}Hz"); + } if (!AudioDevice.SupportsFormat(format)) + { throw new InvalidOperationException($"This sound device does not support SoundIOFormat.{Enum.GetName(typeof(SoundIOFormat), format)}"); + } AudioStream = AudioDevice.CreateOutStream(); - AudioStream.Name = $"SwitchAudioTrack_{TrackID}"; + AudioStream.Name = $"SwitchAudioTrack_{TrackID}"; + AudioStream.Layout = SoundIOChannelLayout.GetDefault(channelCount); + AudioStream.Format = format; AudioStream.SampleRate = sampleRate; - AudioStream.Layout = SoundIOChannelLayout.GetDefault(channelCount); - AudioStream.Format = format; AudioStream.WriteCallback = WriteCallback; - //AudioStream.ErrorCallback = ErrorCallback; - //AudioStream.UnderflowCallback = UnderflowCallback; BufferReleased += callback; @@ -113,38 +121,38 @@ namespace Ryujinx.Audio.SoundIo /// The maximum amount of frames that can be written to the audio backend private unsafe void WriteCallback(int minFrameCount, int maxFrameCount) { - var bytesPerFrame = AudioStream.BytesPerFrame; - var bytesPerSample = (uint)AudioStream.BytesPerSample; + int bytesPerFrame = AudioStream.BytesPerFrame; + uint bytesPerSample = (uint)AudioStream.BytesPerSample; - var bufferedFrames = m_Buffer.Length / bytesPerFrame; - var bufferedSamples = m_Buffer.Length / bytesPerSample; + int bufferedFrames = m_Buffer.Length / bytesPerFrame; + long bufferedSamples = m_Buffer.Length / bytesPerSample; - var frameCount = Math.Min(bufferedFrames, maxFrameCount); + int frameCount = Math.Min(bufferedFrames, maxFrameCount); if (frameCount == 0) return; - var areas = AudioStream.BeginWrite(ref frameCount); - var channelCount = areas.ChannelCount; + SoundIOChannelAreas areas = AudioStream.BeginWrite(ref frameCount); + int channelCount = areas.ChannelCount; - var samples = new byte[frameCount * bytesPerFrame]; + byte[] samples = new byte[frameCount * bytesPerFrame]; m_Buffer.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. - + // Mono if (channelCount == 1) { - var area = areas.GetArea(0); + SoundIOChannelArea area = areas.GetArea(0); fixed (byte* srcptr = samples) { if (bytesPerSample == 1) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { ((byte*)area.Pointer)[0] = srcptr[frame * bytesPerFrame]; @@ -153,7 +161,7 @@ namespace Ryujinx.Audio.SoundIo } else if (bytesPerSample == 2) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { ((short*)area.Pointer)[0] = ((short*)srcptr)[frame * bytesPerFrame >> 1]; @@ -162,7 +170,7 @@ namespace Ryujinx.Audio.SoundIo } else if (bytesPerSample == 4) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { ((int*)area.Pointer)[0] = ((int*)srcptr)[frame * bytesPerFrame >> 2]; @@ -171,7 +179,7 @@ namespace Ryujinx.Audio.SoundIo } else { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { Unsafe.CopyBlockUnaligned((byte*)area.Pointer, srcptr + (frame * bytesPerFrame), bytesPerSample); @@ -183,14 +191,14 @@ namespace Ryujinx.Audio.SoundIo // Stereo else if (channelCount == 2) { - var area1 = areas.GetArea(0); - var area2 = areas.GetArea(1); + SoundIOChannelArea area1 = areas.GetArea(0); + SoundIOChannelArea area2 = areas.GetArea(1); fixed (byte* srcptr = samples) { if (bytesPerSample == 1) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { // Channel 1 ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; @@ -204,7 +212,7 @@ namespace Ryujinx.Audio.SoundIo } else if (bytesPerSample == 2) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { // Channel 1 ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; @@ -218,7 +226,7 @@ namespace Ryujinx.Audio.SoundIo } else if (bytesPerSample == 4) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { // Channel 1 ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; @@ -232,7 +240,7 @@ namespace Ryujinx.Audio.SoundIo } else { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { // Channel 1 Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); @@ -249,18 +257,18 @@ namespace Ryujinx.Audio.SoundIo // Surround else if (channelCount == 6) { - var area1 = areas.GetArea(0); - var area2 = areas.GetArea(1); - var area3 = areas.GetArea(2); - var area4 = areas.GetArea(3); - var area5 = areas.GetArea(4); - var area6 = areas.GetArea(5); + SoundIOChannelArea area1 = areas.GetArea(0); + SoundIOChannelArea area2 = areas.GetArea(1); + SoundIOChannelArea area3 = areas.GetArea(2); + SoundIOChannelArea area4 = areas.GetArea(3); + SoundIOChannelArea area5 = areas.GetArea(4); + SoundIOChannelArea area6 = areas.GetArea(5); fixed (byte* srcptr = samples) { if (bytesPerSample == 1) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { // Channel 1 ((byte*)area1.Pointer)[0] = srcptr[(frame * bytesPerFrame) + 0]; @@ -290,7 +298,7 @@ namespace Ryujinx.Audio.SoundIo } else if (bytesPerSample == 2) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { // Channel 1 ((short*)area1.Pointer)[0] = ((short*)srcptr)[(frame * bytesPerFrame >> 1) + 0]; @@ -320,7 +328,7 @@ namespace Ryujinx.Audio.SoundIo } else if (bytesPerSample == 4) { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { // Channel 1 ((int*)area1.Pointer)[0] = ((int*)srcptr)[(frame * bytesPerFrame >> 2) + 0]; @@ -350,7 +358,7 @@ namespace Ryujinx.Audio.SoundIo } else { - for (var frame = 0; frame < frameCount; frame++) + for (int frame = 0; frame < frameCount; frame++) { // Channel 1 Unsafe.CopyBlockUnaligned((byte*)area1.Pointer, srcptr + (frame * bytesPerFrame) + (0 * bytesPerSample), bytesPerSample); @@ -383,22 +391,22 @@ namespace Ryujinx.Audio.SoundIo // Every other channel count else { - var channels = new SoundIOChannelArea[channelCount]; + SoundIOChannelArea[] channels = new SoundIOChannelArea[channelCount]; // Obtain the channel area for each channel - for (var i = 0; i < channelCount; i++) + for (int i = 0; i < channelCount; i++) channels[i] = areas.GetArea(i); fixed (byte* srcptr = samples) { - for (var frame = 0; frame < frameCount; frame++) - for (var channel = 0; channel < areas.ChannelCount; channel++) - { - // Copy channel by channel, frame by frame. This is slow! - Unsafe.CopyBlockUnaligned((byte*)channels[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample); + for (int frame = 0; frame < frameCount; frame++) + for (int channel = 0; channel < areas.ChannelCount; channel++) + { + // Copy channel by channel, frame by frame. This is slow! + Unsafe.CopyBlockUnaligned((byte*)channels[channel].Pointer, srcptr + (frame * bytesPerFrame) + (channel * bytesPerSample), bytesPerSample); - channels[channel].Pointer += channels[channel].Step; - } + channels[channel].Pointer += channels[channel].Step; + } } } @@ -414,6 +422,7 @@ namespace Ryujinx.Audio.SoundIo private void UpdateReleasedBuffers(int bytesRead) { bool bufferReleased = false; + while (bytesRead > 0) { if (m_ReservedBuffers.TryPeek(out SoundIoBuffer buffer)) @@ -440,29 +449,15 @@ namespace Ryujinx.Audio.SoundIo } } - /// - /// This callback occurs when the sound device encounters an error - /// - private void ErrorCallback() - { - - } - - /// - /// This callback occurs when the sound device runs out of buffered audio data to play - /// - private void UnderflowCallback() - { - - } - /// /// Starts audio playback /// public void Start() { if (AudioStream == null) + { return; + } AudioStream.Start(); AudioStream.Pause(false); @@ -476,7 +471,9 @@ namespace Ryujinx.Audio.SoundIo public void Stop() { if (AudioStream == null) + { return; + } AudioStream.Pause(true); AudioContext.FlushEvents(); @@ -492,13 +489,15 @@ namespace Ryujinx.Audio.SoundIo public void AppendBuffer(long bufferTag, T[] buffer) { if (AudioStream == null) + { return; + } // Calculate the size of the audio samples - var size = Unsafe.SizeOf(); + int size = Unsafe.SizeOf(); // Calculate the amount of bytes to copy from the buffer - var bytesToCopy = size * buffer.Length; + int bytesToCopy = size * buffer.Length; // Copy the memory to our ring buffer m_Buffer.Write(buffer, 0, bytesToCopy); diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs index 43dbc7398a..007dbf0cfb 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs @@ -30,7 +30,14 @@ namespace Ryujinx.Audio.SoundIo /// private SoundIODevice m_Device; + /// + /// + /// private ConcurrentQueue m_Queue; + + /// + /// + /// private ConcurrentDictionary m_TrackList; /// @@ -54,11 +61,12 @@ namespace Ryujinx.Audio.SoundIo /// The maximum amount of tracks that can be created public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize) { + m_Size = 0; m_Context = context; - m_Device = device; - m_Size = 0; + m_Device = device; m_MaxSize = maxSize; - m_Queue = new ConcurrentQueue(); + + m_Queue = new ConcurrentQueue(); m_TrackList = new ConcurrentDictionary(); } @@ -74,7 +82,7 @@ namespace Ryujinx.Audio.SoundIo var trackCollection = Enumerable.Range(0, initialCapacity) .Select(TrackFactory); - m_Size = initialCapacity; + m_Size = initialCapacity; m_Queue = new ConcurrentQueue(trackCollection); } @@ -87,7 +95,7 @@ namespace Ryujinx.Audio.SoundIo private SoundIoAudioTrack TrackFactory(int trackId) { // Create a new AudioTrack - var track = new SoundIoAudioTrack(trackId, m_Context, m_Device); + SoundIoAudioTrack track = new SoundIoAudioTrack(trackId, m_Context, m_Device); // Keep track of issued tracks m_TrackList[trackId] = track; @@ -103,11 +111,15 @@ namespace Ryujinx.Audio.SoundIo { // If we have a track available, reuse it if (m_Queue.TryDequeue(out SoundIoAudioTrack track)) + { return track; - + } + // Have we reached the maximum size of our pool? if (m_Size >= m_MaxSize) + { return null; + } // We don't have any pooled tracks, so create a new one return TrackFactory(m_Size++); @@ -120,7 +132,9 @@ namespace Ryujinx.Audio.SoundIo public SoundIoAudioTrack Get(int trackId) { if (m_TrackList.TryGetValue(trackId, out SoundIoAudioTrack track)) + { return track; + } return null; } @@ -133,6 +147,7 @@ namespace Ryujinx.Audio.SoundIo public bool TryGet(out SoundIoAudioTrack track) { track = Get(); + return track != null; } diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs index 2ac61426a5..2a6190b53b 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoBuffer.cs @@ -22,7 +22,7 @@ /// The size of the buffer public SoundIoBuffer(long tag, int length) { - Tag = tag; + Tag = tag; Length = length; } } diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs index c0adc0405c..b288502132 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoRingBuffer.cs @@ -8,10 +8,10 @@ namespace Ryujinx.Audio.SoundIo internal class SoundIoRingBuffer { private byte[] m_Buffer; - private int m_HeadOffset; - private int m_TailOffset; - private int m_Size; - + private int m_Size; + private int m_HeadOffset; + private int m_TailOffset; + /// /// Gets the available bytes in the ring buffer /// @@ -42,7 +42,7 @@ namespace Ryujinx.Audio.SoundIo /// public void Clear() { - m_Size = 0; + m_Size = 0; m_HeadOffset = 0; m_TailOffset = 0; } @@ -56,10 +56,14 @@ namespace Ryujinx.Audio.SoundIo lock (this) { if (size > m_Size) + { size = m_Size; + } if (size == 0) + { return; + } m_HeadOffset = (m_HeadOffset + size) % m_Buffer.Length; m_Size -= size; @@ -94,7 +98,7 @@ namespace Ryujinx.Audio.SoundIo } } - m_Buffer = buffer; + m_Buffer = buffer; m_HeadOffset = 0; m_TailOffset = m_Size; } @@ -109,7 +113,9 @@ namespace Ryujinx.Audio.SoundIo public void Write(T[] buffer, int index, int count) { if (count == 0) + { return; + } lock (this) { @@ -154,10 +160,14 @@ namespace Ryujinx.Audio.SoundIo lock (this) { if (count > m_Size) + { count = m_Size; + } if (count == 0) + { return 0; + } if (m_HeadOffset < m_TailOffset) { diff --git a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs index ced918f673..fa50f2395f 100644 --- a/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs +++ b/Ryujinx.HLE/HOS/Services/Aud/AudioRenderer/IAudioRenderer.cs @@ -318,11 +318,9 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer continue; } - int OutOffset = 0; - - int PendingSamples = MixBufferSamplesCount; - - float Volume = Voice.Volume; + int OutOffset = 0; + int PendingSamples = MixBufferSamplesCount; + float Volume = Voice.Volume; while (PendingSamples > 0) { @@ -419,7 +417,7 @@ namespace Ryujinx.HLE.HOS.Services.Aud.AudioRenderer // Perform Saturation using SSE2 if supported if (Sse2.IsSupported) { - fixed (int* inptr = Buffer) + fixed (int* inptr = Buffer) fixed (short* outptr = Output) { for (; Offset + 32 <= Buffer.Length; Offset += 32) diff --git a/Ryujinx/Program.cs b/Ryujinx/Program.cs index 4f831bea0e..f1d0a2ff9d 100644 --- a/Ryujinx/Program.cs +++ b/Ryujinx/Program.cs @@ -93,11 +93,17 @@ namespace Ryujinx private static IAalOutput InitializeAudioEngine() { if (SoundIoAudioOut.IsSupported) + { return new SoundIoAudioOut(); + } else if (OpenALAudioOut.IsSupported) + { return new OpenALAudioOut(); - - return new DummyAudioOut(); + } + else + { + return new DummyAudioOut(); + } } } }