2018-03-15 20:06:24 -04:00
using OpenTK.Audio ;
using OpenTK.Audio.OpenAL ;
using System ;
using System.Collections.Concurrent ;
2018-07-14 22:57:41 -04:00
using System.Runtime.InteropServices ;
2018-03-19 14:58:46 -04:00
using System.Threading ;
2018-03-15 20:06:24 -04:00
2018-11-14 21:22:50 -05:00
namespace Ryujinx.Audio
2018-03-15 20:06:24 -04:00
{
2018-11-14 21:22:50 -05:00
/// <summary>
/// An audio renderer that uses OpenAL as the audio backend
/// </summary>
2018-08-16 19:47:36 -04:00
public class OpenALAudioOut : IAalOutput , IDisposable
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
/// <summary>
/// The maximum amount of tracks we can issue simultaneously
/// </summary>
2018-03-15 20:06:24 -04:00
private const int MaxTracks = 256 ;
2019-10-11 11:54:29 -04:00
/// <summary>
/// The <see cref="OpenTK.Audio"/> audio context
/// </summary>
private AudioContext _context ;
2018-03-19 14:58:46 -04:00
2019-10-11 11:54:29 -04:00
/// <summary>
/// An object pool containing <see cref="OpenALAudioTrack"/> objects
/// </summary>
private ConcurrentDictionary < int , OpenALAudioTrack > _tracks ;
2018-03-15 20:06:24 -04:00
2019-10-11 11:54:29 -04:00
/// <summary>
/// True if the thread need to keep polling
/// </summary>
private bool _keepPolling ;
2018-03-19 14:58:46 -04:00
2019-10-11 11:54:29 -04:00
/// <summary>
/// The poller thread audio context
/// </summary>
private Thread _audioPollerThread ;
2018-03-19 14:58:46 -04:00
2019-10-11 11:54:29 -04:00
/// <summary>
/// The volume of audio renderer
/// </summary>
private float _volume = 1.0f ;
2018-03-19 14:58:46 -04:00
2019-10-11 11:54:29 -04:00
/// <summary>
/// True if the volume of audio renderer have changed
/// </summary>
private bool _volumeChanged ;
2018-03-19 14:58:46 -04:00
2018-11-14 21:22:50 -05:00
/// <summary>
2019-10-11 11:54:29 -04:00
/// True if OpenAL is supported on the device
2018-11-14 21:22:50 -05:00
/// </summary>
public static bool IsSupported
{
get
{
try
{
return AudioContext . AvailableDevices . Count > 0 ;
}
catch
{
return false ;
}
}
}
2019-10-11 11:54:29 -04:00
public OpenALAudioOut ( )
{
_context = new AudioContext ( ) ;
_tracks = new ConcurrentDictionary < int , OpenALAudioTrack > ( ) ;
_keepPolling = true ;
2020-01-12 19:21:54 -05:00
_audioPollerThread = new Thread ( AudioPollerWork )
{
Name = "Audio.PollerThread"
} ;
2019-10-11 11:54:29 -04:00
_audioPollerThread . Start ( ) ;
}
2018-03-19 14:58:46 -04:00
private void AudioPollerWork ( )
{
do
{
2019-10-11 11:54:29 -04:00
foreach ( OpenALAudioTrack track in _tracks . Values )
2018-03-19 14:58:46 -04:00
{
2019-10-11 11:54:29 -04:00
lock ( track )
2018-10-12 17:47:53 -04:00
{
2019-10-11 11:54:29 -04:00
track . CallReleaseCallbackIfNeeded ( ) ;
2018-10-12 17:47:53 -04:00
}
2018-03-19 14:58:46 -04:00
}
2019-07-01 22:39:22 -04:00
// If it's not slept it will waste cycles.
2018-07-31 23:48:49 -04:00
Thread . Sleep ( 10 ) ;
2018-03-19 14:58:46 -04:00
}
2019-10-11 11:54:29 -04:00
while ( _keepPolling ) ;
2018-08-16 19:47:36 -04:00
2019-10-11 11:54:29 -04:00
foreach ( OpenALAudioTrack track in _tracks . Values )
2018-08-16 19:47:36 -04:00
{
2019-10-11 11:54:29 -04:00
track . Dispose ( ) ;
2018-08-16 19:47:36 -04:00
}
2019-10-11 11:54:29 -04:00
_tracks . Clear ( ) ;
2020-02-06 06:38:24 -05:00
_context . Dispose ( ) ;
2018-03-15 20:06:24 -04:00
}
2020-08-18 15:03:55 -04:00
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 ;
}
2019-10-11 11:54:29 -04:00
/// <summary>
/// Creates a new audio track with the specified parameters
/// </summary>
/// <param name="sampleRate">The requested sample rate</param>
2020-08-18 15:03:55 -04:00
/// <param name="hardwareChannels">The requested hardware channels</param>
/// <param name="virtualChannels">The requested virtual channels</param>
2019-10-11 11:54:29 -04: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>
2020-08-18 15:03:55 -04:00
/// <returns>The created track's Track ID</returns>
public int OpenHardwareTrack ( int sampleRate , int hardwareChannels , int virtualChannels , ReleaseCallback callback )
2018-03-15 20:06:24 -04:00
{
2020-08-18 15:03:55 -04:00
OpenALAudioTrack track = new OpenALAudioTrack ( sampleRate , GetALFormat ( hardwareChannels ) , hardwareChannels , virtualChannels , callback ) ;
2018-03-15 20:06:24 -04:00
2019-10-11 11:54:29 -04:00
for ( int id = 0 ; id < MaxTracks ; id + + )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _tracks . TryAdd ( id , track ) )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
return id ;
2018-03-15 20:06:24 -04:00
}
}
return - 1 ;
}
2019-10-11 11:54:29 -04:00
private ALFormat GetALFormat ( int channels )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
switch ( channels )
2018-07-09 21:49:07 -04:00
{
2018-07-14 22:57:41 -04:00
case 1 : return ALFormat . Mono16 ;
case 2 : return ALFormat . Stereo16 ;
case 6 : return ALFormat . Multi51Chn16Ext ;
2018-07-09 21:49:07 -04:00
}
2018-03-15 20:06:24 -04:00
2019-10-11 11:54:29 -04:00
throw new ArgumentOutOfRangeException ( nameof ( channels ) ) ;
2018-03-15 20:06:24 -04:00
}
2019-10-11 11:54:29 -04: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-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _tracks . TryRemove ( trackId , out OpenALAudioTrack track ) )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
lock ( track )
2018-09-17 23:12:47 -04:00
{
2019-10-11 11:54:29 -04:00
track . Dispose ( ) ;
2018-09-17 23:12:47 -04:00
}
2018-03-15 20:06:24 -04:00
}
}
2019-10-11 11:54:29 -04: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-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
lock ( track )
2018-09-17 23:12:47 -04:00
{
2019-10-11 11:54:29 -04:00
return track . ContainsBuffer ( bufferTag ) ;
2018-09-17 23:12:47 -04:00
}
2018-03-15 20:06:24 -04:00
}
2018-07-09 21:49:07 -04:00
2018-03-15 23:42:44 -04:00
return false ;
2018-03-15 20:06:24 -04:00
}
2019-10-11 11:54:29 -04: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-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
lock ( track )
2018-09-17 23:12:47 -04:00
{
2019-10-11 11:54:29 -04:00
return track . GetReleasedBuffers ( maxCount ) ;
2018-09-17 23:12:47 -04:00
}
2018-03-15 20:06:24 -04:00
}
2018-07-09 21:49:07 -04:00
2018-03-15 23:42:44 -04:00
return null ;
2018-03-15 20:06:24 -04:00
}
2019-10-11 11:54:29 -04: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-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
lock ( track )
2018-09-17 23:12:47 -04:00
{
2019-10-11 11:54:29 -04:00
int bufferId = track . AppendBuffer ( bufferTag ) ;
2018-03-15 23:42:44 -04:00
2020-08-18 15:03:55 -04:00
// 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 ) ;
}
2018-03-15 23:42:44 -04:00
2019-10-11 11:54:29 -04:00
AL . SourceQueueBuffer ( track . SourceId , bufferId ) ;
2018-03-15 23:42:44 -04:00
2019-10-11 11:54:29 -04:00
StartPlaybackIfNeeded ( track ) ;
2018-09-17 23:12:47 -04:00
}
2018-03-15 20:06:24 -04:00
}
}
2019-10-11 11:54:29 -04: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-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
lock ( track )
2018-09-17 23:12:47 -04:00
{
2019-10-11 11:54:29 -04:00
track . State = PlaybackState . Playing ;
2018-03-15 20:06:24 -04:00
2019-10-11 11:54:29 -04:00
StartPlaybackIfNeeded ( track ) ;
2018-09-17 23:12:47 -04:00
}
2018-03-15 20:06:24 -04:00
}
}
2019-10-11 11:54:29 -04:00
private void StartPlaybackIfNeeded ( OpenALAudioTrack track )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
AL . GetSource ( track . SourceId , ALGetSourcei . SourceState , out int stateInt ) ;
2018-03-15 20:06:24 -04:00
2019-10-11 11:54:29 -04:00
ALSourceState State = ( ALSourceState ) stateInt ;
2018-03-15 20:06:24 -04:00
2019-10-11 11:54:29 -04:00
if ( State ! = ALSourceState . Playing & & track . State = = PlaybackState . Playing )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _volumeChanged )
{
AL . Source ( track . SourceId , ALSourcef . Gain , _volume ) ;
_volumeChanged = false ;
}
AL . SourcePlay ( track . SourceId ) ;
2018-03-15 20:06:24 -04:00
}
}
2019-10-11 11:54:29 -04: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-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
lock ( track )
2018-09-17 23:12:47 -04:00
{
2019-10-11 11:54:29 -04:00
track . State = PlaybackState . Stopped ;
2018-03-15 20:06:24 -04:00
2019-10-11 11:54:29 -04:00
AL . SourceStop ( track . SourceId ) ;
2018-09-17 23:12:47 -04:00
}
2018-03-15 20:06:24 -04:00
}
}
2019-10-11 11:54:29 -04: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-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
if ( _tracks . TryGetValue ( trackId , out OpenALAudioTrack track ) )
2018-03-15 20:06:24 -04:00
{
2019-10-11 11:54:29 -04:00
return track . State ;
2018-03-15 20:06:24 -04:00
}
return PlaybackState . Stopped ;
}
2018-08-16 19:47:36 -04:00
public void Dispose ( )
{
Dispose ( true ) ;
}
2019-10-11 11:54:29 -04:00
protected virtual void Dispose ( bool disposing )
2018-08-16 19:47:36 -04:00
{
2019-10-11 11:54:29 -04:00
if ( disposing )
2018-08-16 19:47:36 -04:00
{
2019-10-11 11:54:29 -04:00
_keepPolling = false ;
2018-08-16 19:47:36 -04:00
}
}
2018-03-15 20:06:24 -04:00
}
}