From 5b26e4ef94afca8450f07c42393180e3c97f9c00 Mon Sep 17 00:00:00 2001 From: Mary Date: Tue, 18 Aug 2020 21:03:55 +0200 Subject: Misc audio fixes (#1348) Changes: Implement software surround downmixing (fix #796). Fix a crash when no audio renderer were created when stopping emulation. NOTE: This PR also disable support of 5.1 surround on the OpenAL backend as we cannot detect if the hardware directly support it. (the downmixing applied by OpenAL on Windows is terribly slow) --- Ryujinx.Audio/Renderers/DummyAudioOut.cs | 11 +++- Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs | 49 ++++++++++++-- Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs | 8 ++- Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs | 12 +++- .../Renderers/SoundIo/SoundIoAudioTrack.cs | 76 ++++++++++++++++++---- 5 files changed, 131 insertions(+), 25 deletions(-) (limited to 'Ryujinx.Audio/Renderers') diff --git a/Ryujinx.Audio/Renderers/DummyAudioOut.cs b/Ryujinx.Audio/Renderers/DummyAudioOut.cs index 10943ae6..2698b928 100644 --- a/Ryujinx.Audio/Renderers/DummyAudioOut.cs +++ b/Ryujinx.Audio/Renderers/DummyAudioOut.cs @@ -30,7 +30,12 @@ namespace Ryujinx.Audio public PlaybackState GetState(int trackId) => PlaybackState.Stopped; - public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) + public bool SupportsChannelCount(int channels) + { + return true; + } + + public int OpenHardwareTrack(int sampleRate, int hardwareChannels, int virtualChannels, ReleaseCallback callback) { if (!_trackIds.TryDequeue(out int trackId)) { @@ -67,11 +72,11 @@ namespace Ryujinx.Audio return bufferTags.ToArray(); } - public void AppendBuffer(int trackID, long bufferTag, T[] buffer) where T : struct + public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct { _buffers.Enqueue(bufferTag); - if (_releaseCallbacks.TryGetValue(trackID, out var callback)) + if (_releaseCallbacks.TryGetValue(trackId, out var callback)) { callback?.Invoke(); } diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs index ea5ce621..fe82fced 100644 --- a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs +++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs @@ -104,15 +104,24 @@ namespace Ryujinx.Audio _context.Dispose(); } + public bool SupportsChannelCount(int channels) + { + // NOTE: OpenAL doesn't give us a way to know if the 5.1 setup is supported by hardware or actually emulated. + // TODO: find a way to determine hardware support. + return channels == 1 || channels == 2; + } + /// /// Creates a new audio track with the specified parameters /// /// The requested sample rate - /// The requested channels + /// The requested hardware channels + /// The requested virtual channels /// A that represents the delegate to invoke when a buffer has been released by the audio track - public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) + /// The created track's Track ID + public int OpenHardwareTrack(int sampleRate, int hardwareChannels, int virtualChannels, ReleaseCallback callback) { - OpenALAudioTrack track = new OpenALAudioTrack(sampleRate, GetALFormat(channels), callback); + OpenALAudioTrack track = new OpenALAudioTrack(sampleRate, GetALFormat(hardwareChannels), hardwareChannels, virtualChannels, callback); for (int id = 0; id < MaxTracks; id++) { @@ -204,9 +213,37 @@ namespace Ryujinx.Audio { int bufferId = track.AppendBuffer(bufferTag); - int size = buffer.Length * Marshal.SizeOf(); - - AL.BufferData(bufferId, track.Format, buffer, size, track.SampleRate); + // Do we need to downmix? + if (track.HardwareChannels != track.VirtualChannels) + { + short[] downmixedBuffer; + + ReadOnlySpan bufferPCM16 = MemoryMarshal.Cast(buffer); + + if (track.VirtualChannels == 6) + { + downmixedBuffer = Downmixing.DownMixSurroundToStereo(bufferPCM16); + + if (track.HardwareChannels == 1) + { + downmixedBuffer = Downmixing.DownMixStereoToMono(downmixedBuffer); + } + } + else if (track.VirtualChannels == 2) + { + downmixedBuffer = Downmixing.DownMixStereoToMono(bufferPCM16); + } + else + { + throw new NotImplementedException($"Downmixing from {track.VirtualChannels} to {track.HardwareChannels} not implemented!"); + } + + AL.BufferData(bufferId, track.Format, downmixedBuffer, downmixedBuffer.Length * sizeof(ushort), track.SampleRate); + } + else + { + AL.BufferData(bufferId, track.Format, buffer, buffer.Length * sizeof(ushort), track.SampleRate); + } AL.SourceQueueBuffer(track.SourceId, bufferId); diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs index 8629dc96..2f150998 100644 --- a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs +++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs @@ -12,6 +12,9 @@ namespace Ryujinx.Audio public ALFormat Format { get; private set; } public PlaybackState State { get; set; } + public int HardwareChannels { get; } + public int VirtualChannels { get; } + private ReleaseCallback _callback; private ConcurrentDictionary _buffers; @@ -21,13 +24,16 @@ namespace Ryujinx.Audio private bool _disposed; - public OpenALAudioTrack(int sampleRate, ALFormat format, ReleaseCallback callback) + public OpenALAudioTrack(int sampleRate, ALFormat format, int hardwareChannels, int virtualChannels, ReleaseCallback callback) { SampleRate = sampleRate; Format = format; State = PlaybackState.Stopped; SourceId = AL.GenSource(); + HardwareChannels = hardwareChannels; + VirtualChannels = virtualChannels; + _callback = callback; _buffers = new ConcurrentDictionary(); diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs index 1e487a6d..fa3961e4 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs @@ -65,14 +65,20 @@ namespace Ryujinx.Audio _trackPool = new SoundIoAudioTrackPool(_audioContext, _audioDevice, MaximumTracks); } + public bool SupportsChannelCount(int channels) + { + return _audioDevice.SupportsChannelCount(channels); + } + /// /// Creates a new audio track with the specified parameters /// /// The requested sample rate - /// The requested channels + /// The requested hardware channels + /// The requested virtual channels /// A that represents the delegate to invoke when a buffer has been released by the audio track /// The created track's Track ID - public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) + public int OpenHardwareTrack(int sampleRate, int hardwareChannels, int virtualChannels, ReleaseCallback callback) { if (!_trackPool.TryGet(out SoundIoAudioTrack track)) { @@ -80,7 +86,7 @@ namespace Ryujinx.Audio } // Open the output. We currently only support 16-bit signed LE - track.Open(sampleRate, channels, callback, SoundIOFormat.S16LE); + track.Open(sampleRate, hardwareChannels, virtualChannels, callback, SoundIOFormat.S16LE); return track.TrackID; } diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs index 97ba11d5..6fdeb991 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Concurrent; using System.Linq; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace Ryujinx.Audio.SoundIo { @@ -53,6 +54,9 @@ namespace Ryujinx.Audio.SoundIo /// public ConcurrentQueue ReleasedBuffers { get; private set; } + private int _hardwareChannels; + private int _virtualChannels; + /// /// Constructs a new instance of a /// @@ -75,12 +79,14 @@ namespace Ryujinx.Audio.SoundIo /// Opens the audio track with the specified parameters /// /// The requested sample rate of the track - /// The requested channel count of the track + /// The requested hardware channels + /// The requested virtual channels /// 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, + int hardwareChannels, + int virtualChannels, ReleaseCallback callback, SoundIOFormat format = SoundIOFormat.S16LE) { @@ -100,10 +106,18 @@ namespace Ryujinx.Audio.SoundIo throw new InvalidOperationException($"This sound device does not support SoundIOFormat.{Enum.GetName(typeof(SoundIOFormat), format)}"); } + if (!AudioDevice.SupportsChannelCount(hardwareChannels)) + { + throw new InvalidOperationException($"This sound device does not support channel count {hardwareChannels}"); + } + + _hardwareChannels = hardwareChannels; + _virtualChannels = virtualChannels; + AudioStream = AudioDevice.CreateOutStream(); AudioStream.Name = $"SwitchAudioTrack_{TrackID}"; - AudioStream.Layout = SoundIOChannelLayout.GetDefault(channelCount); + AudioStream.Layout = SoundIOChannelLayout.GetDefault(hardwareChannels); AudioStream.Format = format; AudioStream.SampleRate = sampleRate; @@ -490,24 +504,62 @@ namespace Ryujinx.Audio.SoundIo /// The audio sample type /// The unqiue tag of the buffer being appended /// The buffer to append - public void AppendBuffer(long bufferTag, T[] buffer) + public void AppendBuffer(long bufferTag, T[] buffer) where T: struct { if (AudioStream == null) { return; } - // Calculate the size of the audio samples - int size = Unsafe.SizeOf(); + int sampleSize = Unsafe.SizeOf(); + int targetSize = sampleSize * buffer.Length; + + // Do we need to downmix? + if (_hardwareChannels != _virtualChannels) + { + if (sampleSize != sizeof(short)) + { + throw new NotImplementedException("Downmixing formats other than PCM16 is not supported!"); + } + + short[] downmixedBuffer; + + ReadOnlySpan bufferPCM16 = MemoryMarshal.Cast(buffer); + + if (_virtualChannels == 6) + { + downmixedBuffer = Downmixing.DownMixSurroundToStereo(bufferPCM16); + + if (_hardwareChannels == 1) + { + downmixedBuffer = Downmixing.DownMixStereoToMono(downmixedBuffer); + } + } + else if (_virtualChannels == 2) + { + downmixedBuffer = Downmixing.DownMixStereoToMono(bufferPCM16); + } + else + { + throw new NotImplementedException($"Downmixing from {_virtualChannels} to {_hardwareChannels} not implemented!"); + } + + targetSize = sampleSize * downmixedBuffer.Length; - // Calculate the amount of bytes to copy from the buffer - int bytesToCopy = size * buffer.Length; + // Copy the memory to our ring buffer + m_Buffer.Write(downmixedBuffer, 0, targetSize); - // Copy the memory to our ring buffer - m_Buffer.Write(buffer, 0, bytesToCopy); + // Keep track of "buffered" buffers + m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, targetSize)); + } + else + { + // Copy the memory to our ring buffer + m_Buffer.Write(buffer, 0, targetSize); - // Keep track of "buffered" buffers - m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, bytesToCopy)); + // Keep track of "buffered" buffers + m_ReservedBuffers.Enqueue(new SoundIoBuffer(bufferTag, targetSize)); + } } /// -- cgit v1.2.3