2018-11-14 18:22:50 -08:00
using Ryujinx.Audio.SoundIo ;
using SoundIOSharp ;
using System.Collections.Generic ;
namespace Ryujinx.Audio
{
/// <summary>
/// An audio renderer that uses libsoundio as the audio backend
/// </summary>
public class SoundIoAudioOut : IAalOutput
{
/// <summary>
/// The maximum amount of tracks we can issue simultaneously
/// </summary>
private const int MaximumTracks = 256 ;
/// <summary>
/// The <see cref="SoundIO"/> audio context
/// </summary>
2019-10-11 08:54:29 -07:00
private SoundIO _audioContext ;
2018-11-14 18:22:50 -08:00
/// <summary>
/// The <see cref="SoundIODevice"/> audio device
/// </summary>
2019-10-11 08:54:29 -07:00
private SoundIODevice _audioDevice ;
2018-11-14 18:22:50 -08:00
/// <summary>
/// An object pool containing <see cref="SoundIoAudioTrack"/> objects
/// </summary>
2019-10-11 08:54:29 -07:00
private SoundIoAudioTrackPool _trackPool ;
2018-11-14 18:22:50 -08:00
/// <summary>
2019-10-11 08:54:29 -07:00
/// True if SoundIO is supported on the device
2018-11-14 18:22:50 -08:00
/// </summary>
2018-11-16 19:35:15 -08:00
public static bool IsSupported
{
get
{
2019-02-12 17:59:26 -08:00
return IsSupportedInternal ( ) ;
2018-11-16 19:35:15 -08:00
}
}
2018-11-14 18:22:50 -08:00
/// <summary>
/// Constructs a new instance of a <see cref="SoundIoAudioOut"/>
/// </summary>
public SoundIoAudioOut ( )
{
2019-10-11 08:54:29 -07:00
_audioContext = new SoundIO ( ) ;
2018-11-14 18:22:50 -08:00
2019-10-11 08:54:29 -07:00
_audioContext . Connect ( ) ;
_audioContext . FlushEvents ( ) ;
2018-11-14 18:22:50 -08:00
2019-10-11 08:54:29 -07:00
_audioDevice = FindNonRawDefaultAudioDevice ( _audioContext , true ) ;
_trackPool = new SoundIoAudioTrackPool ( _audioContext , _audioDevice , MaximumTracks ) ;
2018-11-14 18:22:50 -08:00
}
2020-08-18 12:03:55 -07:00
public bool SupportsChannelCount ( int channels )
{
return _audioDevice . SupportsChannelCount ( channels ) ;
}
2018-11-14 18:22:50 -08:00
/// <summary>
/// Creates a new audio track with the specified parameters
/// </summary>
/// <param name="sampleRate">The requested sample rate</param>
2020-08-18 12:03:55 -07:00
/// <param name="hardwareChannels">The requested hardware channels</param>
/// <param name="virtualChannels">The requested virtual channels</param>
2018-11-14 18:22:50 -08:00
/// <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>
2020-08-18 12:03:55 -07:00
public int OpenHardwareTrack ( int sampleRate , int hardwareChannels , int virtualChannels , ReleaseCallback callback )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
if ( ! _trackPool . TryGet ( out SoundIoAudioTrack track ) )
2018-11-14 18:22:50 -08:00
{
return - 1 ;
}
// Open the output. We currently only support 16-bit signed LE
2020-08-18 12:03:55 -07:00
track . Open ( sampleRate , hardwareChannels , virtualChannels , callback , SoundIOFormat . S16LE ) ;
2018-11-14 18:22:50 -08:00
return track . TrackID ;
}
/// <summary>
/// Stops playback and closes the track specified by <paramref name="trackId"/>
/// </summary>
/// <param name="trackId">The ID of the track to close</param>
public void CloseTrack ( int trackId )
{
2019-10-11 08:54:29 -07:00
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
2018-11-14 18:22:50 -08:00
{
// Close and dispose of the track
track . Close ( ) ;
// Recycle the track back into the pool
2019-10-11 08:54:29 -07:00
_trackPool . Put ( track ) ;
2018-11-14 18:22:50 -08:00
}
}
/// <summary>
2019-10-11 08:54:29 -07:00
/// Returns a value indicating whether the specified buffer is currently reserved by the specified track
2018-11-14 18:22:50 -08:00
/// </summary>
2019-10-11 08:54:29 -07:00
/// <param name="trackId">The track to check</param>
/// <param name="bufferTag">The buffer tag to check</param>
public bool ContainsBuffer ( int trackId , long bufferTag )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
return track . ContainsBuffer ( bufferTag ) ;
2018-11-14 18:22:50 -08:00
}
2019-10-11 08:54:29 -07:00
return false ;
2018-11-14 18:22:50 -08:00
}
/// <summary>
2019-10-11 08:54:29 -07:00
/// Gets a list of buffer tags the specified track is no longer reserving
2018-11-14 18:22:50 -08:00
/// </summary>
2019-10-11 08:54:29 -07:00
/// <param name="trackId">The track to retrieve buffer tags from</param>
/// <param name="maxCount">The maximum amount of buffer tags to retrieve</param>
/// <returns>Buffers released by the specified track</returns>
public long [ ] GetReleasedBuffers ( int trackId , int maxCount )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
List < long > bufferTags = new List < long > ( ) ;
while ( maxCount - - > 0 & & track . ReleasedBuffers . TryDequeue ( out long tag ) )
{
bufferTags . Add ( tag ) ;
}
return bufferTags . ToArray ( ) ;
2018-11-14 18:22:50 -08:00
}
2019-10-11 08:54:29 -07:00
return new long [ 0 ] ;
2018-11-14 18:22:50 -08:00
}
/// <summary>
/// Appends an audio buffer to the specified track
/// </summary>
/// <typeparam name="T">The sample type of the buffer</typeparam>
/// <param name="trackId">The track to append the buffer to</param>
/// <param name="bufferTag">The internal tag of the buffer</param>
/// <param name="buffer">The buffer to append to the track</param>
2019-10-11 08:54:29 -07:00
public void AppendBuffer < T > ( int trackId , long bufferTag , T [ ] buffer ) where T : struct
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
2020-11-20 12:59:01 -08:00
{
2018-11-14 18:22:50 -08:00
track . AppendBuffer ( bufferTag , buffer ) ;
}
}
/// <summary>
2019-10-11 08:54:29 -07:00
/// Starts playback
2018-11-14 18:22:50 -08:00
/// </summary>
2019-10-11 08:54:29 -07:00
/// <param name="trackId">The ID of the track to start playback on</param>
public void Start ( int trackId )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
track . Start ( ) ;
2018-11-14 18:22:50 -08:00
}
}
/// <summary>
2019-10-11 08:54:29 -07:00
/// Stops playback
2018-11-14 18:22:50 -08:00
/// </summary>
2019-10-11 08:54:29 -07:00
/// <param name="trackId">The ID of the track to stop playback on</param>
public void Stop ( int trackId )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
2018-11-14 18:22:50 -08:00
{
2019-10-11 08:54:29 -07:00
track . Stop ( ) ;
}
}
2018-11-14 18:22:50 -08:00
2019-10-11 08:54:29 -07:00
/// <summary>
2020-11-20 12:59:01 -08:00
/// Get track buffer count
2019-10-11 08:54:29 -07:00
/// </summary>
2020-11-20 12:59:01 -08:00
/// <param name="trackId">The ID of the track to get buffer count</param>
public uint GetBufferCount ( int trackId )
{
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
{
return track . BufferCount ;
}
return 0 ;
}
2018-11-14 18:22:50 -08:00
2019-10-11 08:54:29 -07:00
/// <summary>
2020-11-20 12:59:01 -08:00
/// Get track played sample count
/// </summary>
/// <param name="trackId">The ID of the track to get played sample</param>
public ulong GetPlayedSampleCount ( int trackId )
{
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
{
return track . PlayedSampleCount ;
}
return 0 ;
}
/// <summary>
/// Flush all track buffers
/// </summary>
/// <param name="trackId">The ID of the track to flush</param>
public bool FlushBuffers ( int trackId )
{
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
{
return track . FlushBuffers ( ) ;
}
return false ;
}
/// <summary>
/// Set track volume
2019-10-11 08:54:29 -07:00
/// </summary>
/// <param name="volume">The volume of the playback</param>
2020-11-20 12:59:01 -08:00
public void SetVolume ( int trackId , float volume )
2019-10-11 08:54:29 -07:00
{
2020-11-20 12:59:01 -08:00
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
2019-10-11 08:54:29 -07:00
{
2020-11-20 12:59:01 -08:00
track . AudioStream . SetVolume ( volume ) ;
2019-10-11 08:54:29 -07:00
}
}
2020-11-20 12:59:01 -08:00
/// <summary>
/// Get track volume
/// </summary>
public float GetVolume ( int trackId )
{
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
{
return track . AudioStream . Volume ;
}
return 1.0f ;
}
2019-10-11 08:54:29 -07:00
/// <summary>
/// Gets the current playback state of the specified track
/// </summary>
/// <param name="trackId">The track to retrieve the playback state for</param>
public PlaybackState GetState ( int trackId )
{
if ( _trackPool . TryGet ( trackId , out SoundIoAudioTrack track ) )
{
return track . State ;
2018-11-14 18:22:50 -08:00
}
2019-10-11 08:54:29 -07:00
return PlaybackState . Stopped ;
2018-11-14 18:22:50 -08:00
}
/// <summary>
/// Releases the unmanaged resources used by the <see cref="SoundIoAudioOut" />
/// </summary>
public void Dispose ( )
{
2019-10-11 08:54:29 -07:00
_trackPool . Dispose ( ) ;
_audioContext . Disconnect ( ) ;
_audioContext . Dispose ( ) ;
2018-11-14 18:22:50 -08:00
}
2019-02-12 17:59:26 -08:00
/// <summary>
/// Searches for a shared version of the default audio device
/// </summary>
/// <param name="audioContext">The <see cref="SoundIO"/> audio context</param>
/// <param name="fallback">Whether to fallback to the raw default audio device if a non-raw device cannot be found</param>
private static SoundIODevice FindNonRawDefaultAudioDevice ( SoundIO audioContext , bool fallback = false )
{
SoundIODevice defaultAudioDevice = audioContext . GetOutputDevice ( audioContext . DefaultOutputDeviceIndex ) ;
2019-07-01 19:39:22 -07:00
if ( ! defaultAudioDevice . IsRaw )
2019-02-12 17:59:26 -08:00
{
return defaultAudioDevice ;
}
2019-07-01 19:39:22 -07:00
for ( int i = 0 ; i < audioContext . BackendCount ; i + + )
2019-02-12 17:59:26 -08:00
{
SoundIODevice audioDevice = audioContext . GetOutputDevice ( i ) ;
if ( audioDevice . Id = = defaultAudioDevice . Id & & ! audioDevice . IsRaw )
{
return audioDevice ;
}
}
return fallback ? defaultAudioDevice : null ;
}
/// <summary>
/// Determines if SoundIO can connect to a supported backend
/// </summary>
/// <returns></returns>
private static bool IsSupportedInternal ( )
{
SoundIO context = null ;
SoundIODevice device = null ;
SoundIOOutStream stream = null ;
bool backendDisconnected = false ;
try
{
context = new SoundIO ( ) ;
context . OnBackendDisconnect = ( i ) = > {
backendDisconnected = true ;
} ;
context . Connect ( ) ;
context . FlushEvents ( ) ;
2019-07-01 19:39:22 -07:00
if ( backendDisconnected )
2019-02-12 17:59:26 -08:00
{
return false ;
}
2019-07-01 19:39:22 -07:00
if ( context . OutputDeviceCount = = 0 )
2019-02-12 17:59:26 -08:00
{
return false ;
}
device = FindNonRawDefaultAudioDevice ( context ) ;
2019-07-01 19:39:22 -07:00
if ( device = = null | | backendDisconnected )
2019-02-12 17:59:26 -08:00
{
return false ;
}
stream = device . CreateOutStream ( ) ;
2019-07-01 19:39:22 -07:00
if ( stream = = null | | backendDisconnected )
2019-02-12 17:59:26 -08:00
{
return false ;
}
return true ;
}
catch
{
return false ;
}
finally
{
2019-07-01 19:39:22 -07:00
if ( stream ! = null )
2019-02-12 17:59:26 -08:00
{
stream . Dispose ( ) ;
}
2019-07-01 19:39:22 -07:00
if ( context ! = null )
2019-02-12 17:59:26 -08:00
{
context . Dispose ( ) ;
}
}
}
2018-11-14 18:22:50 -08:00
}
}