Ryujinx/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioTrackPool.cs
jduncanator 8275bc3c08 Implement libsoundio as an alternative audio backend (#406)
* Audio: Implement libsoundio as an alternative audio backend

libsoundio will be preferred over OpenAL if it is available on the machine. If neither are available, it will fallback to a dummy audio renderer that outputs no sound.

* Audio: Fix SoundIoRingBuffer documentation

* Audio: Unroll and optimize the audio write callback

Copying one sample at a time is slow, this unrolls the most common audio channel layouts and manually copies the bytes between source and destination. This is over 2x faster than calling CopyBlockUnaligned every sample.

* Audio: Optimize the write callback further

This dramatically reduces the audio buffer copy time. When the sample size is one of handled sample sizes the buffer copy operation is almost 10x faster than CopyBlockAligned.

This works by copying full samples at a time, rather than the individual bytes that make up the sample. This allows for 2x or 4x faster copy operations depending on sample size.

* Audio: Fix typo in Stereo write callback

* Audio: Fix Surround (5.1) audio write callback

* Audio: Update Documentation

* Audio: Use built-in Unsafe.SizeOf<T>()

Built-in `SizeOf<T>()` is 10x faster than our `TypeSize<T>` helper. This also helps reduce code surface area.

* Audio: Keep fixed buffer style consistent

* Audio: Address styling nits

* Audio: More style nits

* Audio: Add additional documentation

* Audio: Move libsoundio bindings internal

As per discussion, moving the libsoundio native bindings into Ryujinx.Audio

* Audio: Bump Target Framework back up to .NET Core 2.1

* Audio: Remove voice mixing optimizations.

Leaves Saturation optimizations in place.
2018-11-15 03:22:50 +01:00

193 lines
6.8 KiB
C#

using SoundIOSharp;
using System;
using System.Collections.Concurrent;
using System.Linq;
namespace Ryujinx.Audio.SoundIo
{
/// <summary>
/// An object pool containing a set of audio tracks
/// </summary>
internal class SoundIoAudioTrackPool : IDisposable
{
/// <summary>
/// The current size of the <see cref="SoundIoAudioTrackPool"/>
/// </summary>
private int m_Size;
/// <summary>
/// The maximum size of the <see cref="SoundIoAudioTrackPool"/>
/// </summary>
private int m_MaxSize;
/// <summary>
/// The <see cref="SoundIO"/> audio context this track pool belongs to
/// </summary>
private SoundIO m_Context;
/// <summary>
/// The <see cref="SoundIODevice"/> audio device this track pool belongs to
/// </summary>
private SoundIODevice m_Device;
/// <summary>
/// The queue that keeps track of the available <see cref="SoundIoAudioTrack"/> in the pool.
/// </summary>
private ConcurrentQueue<SoundIoAudioTrack> m_Queue;
/// <summary>
/// The dictionary providing mapping between a TrackID and <see cref="SoundIoAudioTrack"/>
/// </summary>
private ConcurrentDictionary<int, SoundIoAudioTrack> m_TrackList;
/// <summary>
/// Gets the current size of the <see cref="SoundIoAudioTrackPool"/>
/// </summary>
public int Size { get => m_Size; }
/// <summary>
/// Gets the maximum size of the <see cref="SoundIoAudioTrackPool"/>
/// </summary>
public int MaxSize { get => m_MaxSize; }
/// <summary>
/// Gets a value that indicates whether the <see cref="SoundIoAudioTrackPool"/> is empty
/// </summary>
public bool IsEmpty { get => m_Queue.IsEmpty; }
/// <summary>
/// Constructs a new instance of a <see cref="SoundIoAudioTrackPool"/> that is empty
/// </summary>
/// <param name="maxSize">The maximum amount of tracks that can be created</param>
public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize)
{
m_Size = 0;
m_Context = context;
m_Device = device;
m_MaxSize = maxSize;
m_Queue = new ConcurrentQueue<SoundIoAudioTrack>();
m_TrackList = new ConcurrentDictionary<int, SoundIoAudioTrack>();
}
/// <summary>
/// Constructs a new instance of a <see cref="SoundIoAudioTrackPool"/> that contains
/// the specified amount of <see cref="SoundIoAudioTrack"/>
/// </summary>
/// <param name="maxSize">The maximum amount of tracks that can be created</param>
/// <param name="initialCapacity">The initial number of tracks that the pool contains</param>
public SoundIoAudioTrackPool(SoundIO context, SoundIODevice device, int maxSize, int initialCapacity)
: this(context, device, maxSize)
{
var trackCollection = Enumerable.Range(0, initialCapacity)
.Select(TrackFactory);
m_Size = initialCapacity;
m_Queue = new ConcurrentQueue<SoundIoAudioTrack>(trackCollection);
}
/// <summary>
/// Creates a new <see cref="SoundIoAudioTrack"/> with the proper AudioContext and AudioDevice
/// and the specified <paramref name="trackId" />
/// </summary>
/// <param name="trackId">The ID of the track to be created</param>
/// <returns>A new AudioTrack with the specified ID</returns>
private SoundIoAudioTrack TrackFactory(int trackId)
{
// Create a new AudioTrack
SoundIoAudioTrack track = new SoundIoAudioTrack(trackId, m_Context, m_Device);
// Keep track of issued tracks
m_TrackList[trackId] = track;
return track;
}
/// <summary>
/// Retrieves a <see cref="SoundIoAudioTrack"/> from the pool
/// </summary>
/// <returns>An AudioTrack from the pool</returns>
public SoundIoAudioTrack Get()
{
// 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++);
}
/// <summary>
/// Retrieves the <see cref="SoundIoAudioTrack"/> associated with the specified <paramref name="trackId"/> from the pool
/// </summary>
/// <param name="trackId">The ID of the track to retrieve</param>
public SoundIoAudioTrack Get(int trackId)
{
if (m_TrackList.TryGetValue(trackId, out SoundIoAudioTrack track))
{
return track;
}
return null;
}
/// <summary>
/// Attempers to get a <see cref="SoundIoAudioTrack"/> from the pool
/// </summary>
/// <param name="track">The track retrieved from the pool</param>
/// <returns>True if retrieve was successful</returns>
public bool TryGet(out SoundIoAudioTrack track)
{
track = Get();
return track != null;
}
/// <summary>
/// Attempts to get the <see cref="SoundIoAudioTrack" /> associated with the specified <paramref name="trackId"/> from the pool
/// </summary>
/// <param name="trackId">The ID of the track to retrieve</param>
/// <param name="track">The track retrieved from the pool</param>
public bool TryGet(int trackId, out SoundIoAudioTrack track)
{
return m_TrackList.TryGetValue(trackId, out track);
}
/// <summary>
/// Returns an <see cref="SoundIoAudioTrack"/> back to the pool for reuse
/// </summary>
/// <param name="track">The track to be returned to the pool</param>
public void Put(SoundIoAudioTrack track)
{
// Ensure the track is disposed and not playing audio
track.Close();
// Requeue the track for reuse later
m_Queue.Enqueue(track);
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="SoundIoAudioTrackPool" />
/// </summary>
public void Dispose()
{
foreach (var track in m_TrackList)
{
track.Value.Close();
track.Value.Dispose();
}
m_Size = 0;
m_Queue.Clear();
m_TrackList.Clear();
}
}
}