aboutsummaryrefslogtreecommitdiff
path: root/Ryujinx.Audio/Renderers
diff options
context:
space:
mode:
Diffstat (limited to 'Ryujinx.Audio/Renderers')
-rw-r--r--Ryujinx.Audio/Renderers/DummyAudioOut.cs11
-rw-r--r--Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs49
-rw-r--r--Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs8
-rw-r--r--Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs12
-rw-r--r--Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrack.cs76
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>