diff options
Diffstat (limited to 'Ryujinx.Audio/Renderers')
| -rw-r--r-- | Ryujinx.Audio/Renderers/DummyAudioOut.cs | 11 | ||||
| -rw-r--r-- | Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs | 49 | ||||
| -rw-r--r-- | Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs | 8 | ||||
| -rw-r--r-- | Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs | 12 | ||||
| -rw-r--r-- | Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs | 76 |
5 files changed, 131 insertions, 25 deletions
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<T>(int trackID, long bufferTag, T[] buffer) where T : struct + public void AppendBuffer<T>(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; + } + /// <summary> /// Creates a new audio track with the specified parameters /// </summary> /// <param name="sampleRate">The requested sample rate</param> - /// <param name="channels">The requested channels</param> + /// <param name="hardwareChannels">The requested hardware channels</param> + /// <param name="virtualChannels">The requested virtual channels</param> /// <param name="callback">A <see cref="ReleaseCallback" /> that represents the delegate to invoke when a buffer has been released by the audio track</param> - public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) + /// <returns>The created track's Track ID</returns> + 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<T>(); - - AL.BufferData(bufferId, track.Format, buffer, size, track.SampleRate); + // Do we need to downmix? + if (track.HardwareChannels != track.VirtualChannels) + { + short[] downmixedBuffer; + + ReadOnlySpan<short> bufferPCM16 = MemoryMarshal.Cast<T, short>(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<long, int> _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<long, int>(); 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); + } + /// <summary> /// Creates a new audio track with the specified parameters /// </summary> /// <param name="sampleRate">The requested sample rate</param> - /// <param name="channels">The requested channels</param> + /// <param name="hardwareChannels">The requested hardware channels</param> + /// <param name="virtualChannels">The requested virtual channels</param> /// <param name="callback">A <see cref="ReleaseCallback" /> that represents the delegate to invoke when a buffer has been released by the audio track</param> /// <returns>The created track's Track ID</returns> - 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 /// </summary> public ConcurrentQueue<long> ReleasedBuffers { get; private set; } + private int _hardwareChannels; + private int _virtualChannels; + /// <summary> /// Constructs a new instance of a <see cref="SoundIoAudioTrack"/> /// </summary> @@ -75,12 +79,14 @@ namespace Ryujinx.Audio.SoundIo /// Opens the audio track with the specified parameters /// </summary> /// <param name="sampleRate">The requested sample rate of the track</param> - /// <param name="channelCount">The requested channel count of the track</param> + /// <param name="hardwareChannels">The requested hardware channels</param> + /// <param name="virtualChannels">The requested virtual channels</param> /// <param name="callback">A <see cref="ReleaseCallback" /> that represents the delegate to invoke when a buffer has been released by the audio track</param> /// <param name="format">The requested sample format of the track</param> 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 /// <typeparam name="T">The audio sample type</typeparam> /// <param name="bufferTag">The unqiue tag of the buffer being appended</param> /// <param name="buffer">The buffer to append</param> - public void AppendBuffer<T>(long bufferTag, T[] buffer) + public void AppendBuffer<T>(long bufferTag, T[] buffer) where T: struct { if (AudioStream == null) { return; } - // Calculate the size of the audio samples - int size = Unsafe.SizeOf<T>(); + int sampleSize = Unsafe.SizeOf<T>(); + 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<short> bufferPCM16 = MemoryMarshal.Cast<T, short>(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)); + } } /// <summary> |
