2018-03-16 01:06:24 +01:00
using OpenTK.Audio ;
using OpenTK.Audio.OpenAL ;
using System ;
using System.Collections.Concurrent ;
2018-07-15 04:57:41 +02:00
using System.Runtime.InteropServices ;
2018-03-19 19:58:46 +01:00
using System.Threading ;
2018-03-16 01:06:24 +01:00
2018-11-15 03:22:50 +01:00
namespace Ryujinx.Audio
2018-03-16 01:06:24 +01:00
{
2018-11-15 03:22:50 +01:00
/// <summary>
/// An audio renderer that uses OpenAL as the audio backend
/// </summary>
2018-08-17 01:47:36 +02:00
public class OpenALAudioOut : IAalOutput , IDisposable
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
/// <summary>
/// The maximum amount of tracks we can issue simultaneously
/// </summary>
2018-03-16 01:06:24 +01:00
private const int MaxTracks = 256 ;
2019-10-11 17:54:29 +02:00
/// <summary>
/// The <see cref="OpenTK.Audio"/> audio context
/// </summary>
private AudioContext _context ;
2018-03-19 19:58:46 +01:00
2019-10-11 17:54:29 +02:00
/// <summary>
/// An object pool containing <see cref="OpenALAudioTrack"/> objects
/// </summary>
private ConcurrentDictionary < int , OpenALAudioTrack > _tracks ;
2018-03-16 01:06:24 +01:00
2019-10-11 17:54:29 +02:00
/// <summary>
/// True if the thread need to keep polling
/// </summary>
private bool _keepPolling ;
2018-03-19 19:58:46 +01:00
2019-10-11 17:54:29 +02:00
/// <summary>
/// The poller thread audio context
/// </summary>
private Thread _audioPollerThread ;
2018-03-19 19:58:46 +01:00
2019-10-11 17:54:29 +02:00
/// <summary>
/// The volume of audio renderer
/// </summary>
private float _volume = 1.0f ;
2018-03-19 19:58:46 +01:00
2019-10-11 17:54:29 +02:00
/// <summary>
/// True if the volume of audio renderer have changed
/// </summary>
private bool _volumeChanged ;
2018-03-19 19:58:46 +01:00
2018-11-15 03:22:50 +01:00
/// <summary>
2019-10-11 17:54:29 +02:00
/// True if OpenAL is supported on the device
2018-11-15 03:22:50 +01:00
/// </summary>
public static bool IsSupported
{
get
{
try
{
return AudioContext . AvailableDevices . Count > 0 ;
}
catch
{
return false ;
}
}
}
2019-10-11 17:54:29 +02:00
public OpenALAudioOut ( )
{
_context = new AudioContext ( ) ;
_tracks = new ConcurrentDictionary < int , OpenALAudioTrack > ( ) ;
_keepPolling = true ;
2020-01-13 01:21:54 +01:00
_audioPollerThread = new Thread ( AudioPollerWork )
{
Name = "Audio.PollerThread"
} ;
2019-10-11 17:54:29 +02:00
_audioPollerThread . Start ( ) ;
}
2018-03-19 19:58:46 +01:00
private void AudioPollerWork ( )
{
do
{
2019-10-11 17:54:29 +02:00
foreach ( OpenALAudioTrack track in _tracks . Values )
2018-03-19 19:58:46 +01:00
{
2019-10-11 17:54:29 +02:00
lock ( track )
2018-10-12 23:47:53 +02:00
{
2019-10-11 17:54:29 +02:00
track . CallReleaseCallbackIfNeeded ( ) ;
2018-10-12 23:47:53 +02:00
}
2018-03-19 19:58:46 +01:00
}
2019-07-02 04:39:22 +02:00
// If it's not slept it will waste cycles.
2018-08-01 05:48:49 +02:00
Thread . Sleep ( 10 ) ;
2018-03-19 19:58:46 +01:00
}
2019-10-11 17:54:29 +02:00
while ( _keepPolling ) ;
2018-08-17 01:47:36 +02:00
2019-10-11 17:54:29 +02:00
foreach ( OpenALAudioTrack track in _tracks . Values )
2018-08-17 01:47:36 +02:00
{
2019-10-11 17:54:29 +02:00
track . Dispose ( ) ;
2018-08-17 01:47:36 +02:00
}
2019-10-11 17:54:29 +02:00
_tracks . Clear ( ) ;
2018-03-16 01:06:24 +01:00
}
2019-10-11 17:54:29 +02:00
/// <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="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 )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
OpenALAudioTrack track = new OpenALAudioTrack ( sampleRate , GetALFormat ( channels ) , callback ) ;
2018-03-16 01:06:24 +01:00
2019-10-11 17:54:29 +02:00
for ( int id = 0 ; id < MaxTracks ; id + + )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _tracks . TryAdd ( id , track ) )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
return id ;
2018-03-16 01:06:24 +01:00
}
}
return - 1 ;
}
2019-10-11 17:54:29 +02:00
private ALFormat GetALFormat ( int channels )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
switch ( channels )
2018-07-10 03:49:07 +02:00
{
2018-07-15 04:57:41 +02:00
case 1 : return ALFormat . Mono16 ;
case 2 : return ALFormat . Stereo16 ;
case 6 : return ALFormat . Multi51Chn16Ext ;
2018-07-10 03:49:07 +02:00
}
2018-03-16 01:06:24 +01:00
2019-10-11 17:54:29 +02:00
throw new ArgumentOutOfRangeException ( nameof ( channels ) ) ;
2018-03-16 01:06:24 +01:00
}
2019-10-11 17:54:29 +02:00
/// <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 )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _tracks . TryRemove ( trackId , out OpenALAudioTrack track ) )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
lock ( track )
2018-09-18 05:12:47 +02:00
{
2019-10-11 17:54:29 +02:00
track . Dispose ( ) ;
2018-09-18 05:12:47 +02:00
}
2018-03-16 01:06:24 +01:00
}
}
2019-10-11 17:54:29 +02:00
/// <summary>
/// Returns a value indicating whether the specified buffer is currently reserved by the specified track
/// </summary>
/// <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-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
lock ( track )
2018-09-18 05:12:47 +02:00
{
2019-10-11 17:54:29 +02:00
return track . ContainsBuffer ( bufferTag ) ;
2018-09-18 05:12:47 +02:00
}
2018-03-16 01:06:24 +01:00
}
2018-07-10 03:49:07 +02:00
2018-03-16 04:42:44 +01:00
return false ;
2018-03-16 01:06:24 +01:00
}
2019-10-11 17:54:29 +02:00
/// <summary>
/// Gets a list of buffer tags the specified track is no longer reserving
/// </summary>
/// <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-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
lock ( track )
2018-09-18 05:12:47 +02:00
{
2019-10-11 17:54:29 +02:00
return track . GetReleasedBuffers ( maxCount ) ;
2018-09-18 05:12:47 +02:00
}
2018-03-16 01:06:24 +01:00
}
2018-07-10 03:49:07 +02:00
2018-03-16 04:42:44 +01:00
return null ;
2018-03-16 01:06:24 +01:00
}
2019-10-11 17:54:29 +02: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>
public void AppendBuffer < T > ( int trackId , long bufferTag , T [ ] buffer ) where T : struct
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
lock ( track )
2018-09-18 05:12:47 +02:00
{
2019-10-11 17:54:29 +02:00
int bufferId = track . AppendBuffer ( bufferTag ) ;
2018-03-16 04:42:44 +01:00
2019-10-11 17:54:29 +02:00
int size = buffer . Length * Marshal . SizeOf < T > ( ) ;
2018-07-15 04:57:41 +02:00
2019-10-11 17:54:29 +02:00
AL . BufferData ( bufferId , track . Format , buffer , size , track . SampleRate ) ;
2018-03-16 04:42:44 +01:00
2019-10-11 17:54:29 +02:00
AL . SourceQueueBuffer ( track . SourceId , bufferId ) ;
2018-03-16 04:42:44 +01:00
2019-10-11 17:54:29 +02:00
StartPlaybackIfNeeded ( track ) ;
2018-09-18 05:12:47 +02:00
}
2018-03-16 01:06:24 +01:00
}
}
2019-10-11 17:54:29 +02:00
/// <summary>
/// Starts playback
/// </summary>
/// <param name="trackId">The ID of the track to start playback on</param>
public void Start ( int trackId )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
lock ( track )
2018-09-18 05:12:47 +02:00
{
2019-10-11 17:54:29 +02:00
track . State = PlaybackState . Playing ;
2018-03-16 01:06:24 +01:00
2019-10-11 17:54:29 +02:00
StartPlaybackIfNeeded ( track ) ;
2018-09-18 05:12:47 +02:00
}
2018-03-16 01:06:24 +01:00
}
}
2019-10-11 17:54:29 +02:00
private void StartPlaybackIfNeeded ( OpenALAudioTrack track )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
AL . GetSource ( track . SourceId , ALGetSourcei . SourceState , out int stateInt ) ;
2018-03-16 01:06:24 +01:00
2019-10-11 17:54:29 +02:00
ALSourceState State = ( ALSourceState ) stateInt ;
2018-03-16 01:06:24 +01:00
2019-10-11 17:54:29 +02:00
if ( State ! = ALSourceState . Playing & & track . State = = PlaybackState . Playing )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _volumeChanged )
{
AL . Source ( track . SourceId , ALSourcef . Gain , _volume ) ;
_volumeChanged = false ;
}
AL . SourcePlay ( track . SourceId ) ;
2018-03-16 01:06:24 +01:00
}
}
2019-10-11 17:54:29 +02:00
/// <summary>
/// Stops playback
/// </summary>
/// <param name="trackId">The ID of the track to stop playback on</param>
public void Stop ( int trackId )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
lock ( track )
2018-09-18 05:12:47 +02:00
{
2019-10-11 17:54:29 +02:00
track . State = PlaybackState . Stopped ;
2018-03-16 01:06:24 +01:00
2019-10-11 17:54:29 +02:00
AL . SourceStop ( track . SourceId ) ;
2018-09-18 05:12:47 +02:00
}
2018-03-16 01:06:24 +01:00
}
}
2019-10-11 17:54:29 +02:00
/// <summary>
/// Get playback volume
/// </summary>
public float GetVolume ( ) = > _volume ;
/// <summary>
/// Set playback volume
/// </summary>
/// <param name="volume">The volume of the playback</param>
public void SetVolume ( float volume )
{
if ( ! _volumeChanged )
{
_volume = volume ;
_volumeChanged = true ;
}
}
/// <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 )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-16 01:06:24 +01:00
{
2019-10-11 17:54:29 +02:00
return track . State ;
2018-03-16 01:06:24 +01:00
}
return PlaybackState . Stopped ;
}
2018-08-17 01:47:36 +02:00
public void Dispose ( )
{
Dispose ( true ) ;
}
2019-10-11 17:54:29 +02:00
protected virtual void Dispose ( bool disposing )
2018-08-17 01:47:36 +02:00
{
2019-10-11 17:54:29 +02:00
if ( disposing )
2018-08-17 01:47:36 +02:00
{
2019-10-11 17:54:29 +02:00
_keepPolling = false ;
2018-08-17 01:47:36 +02:00
}
}
2018-03-16 01:06:24 +01:00
}
}