From c17e1f99f0d9047681ead1d51ff498cd252c2b3c Mon Sep 17 00:00:00 2001 From: Ac_K Date: Fri, 11 Oct 2019 17:54:29 +0200 Subject: audout:u: Implement SetAudioOutVolume and GetAudioOutVolume (#781) * audout:u: Implement SetAudioOutVolume and GetAudioOutVolume - Implementation of `audout:u` SetAudioOutVolume and GetAudioOutVolume (checked with RE). - Add Get and Set for Volume into audio backends. - Cleanup of all audio backends to follow the `IAalOutput` structure and .NET standard. - Split OpenAL backend into 2 files for consistency. * Address comments * Fix the volume calculation --- Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs | 375 ++++++++++------------- 1 file changed, 159 insertions(+), 216 deletions(-) (limited to 'Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs') diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs index 6e085eed..69f36a4d 100644 --- a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs +++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs @@ -2,7 +2,6 @@ using OpenTK.Audio; using OpenTK.Audio.OpenAL; using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Runtime.InteropServices; using System.Threading; @@ -13,174 +12,43 @@ namespace Ryujinx.Audio /// public class OpenALAudioOut : IAalOutput, IDisposable { + /// + /// The maximum amount of tracks we can issue simultaneously + /// private const int MaxTracks = 256; - private const int MaxReleased = 32; - - private AudioContext Context; - - private class Track : IDisposable - { - public int SourceId { get; private set; } - - public int SampleRate { get; private set; } - - public ALFormat Format { get; private set; } - - private ReleaseCallback Callback; - - public PlaybackState State { get; set; } - - private ConcurrentDictionary Buffers; - - private Queue QueuedTagsQueue; - - private Queue ReleasedTagsQueue; - - private bool Disposed; - - public Track(int SampleRate, ALFormat Format, ReleaseCallback Callback) - { - this.SampleRate = SampleRate; - this.Format = Format; - this.Callback = Callback; - - State = PlaybackState.Stopped; - - SourceId = AL.GenSource(); - - Buffers = new ConcurrentDictionary(); - - QueuedTagsQueue = new Queue(); - - ReleasedTagsQueue = new Queue(); - } - - public bool ContainsBuffer(long Tag) - { - foreach (long QueuedTag in QueuedTagsQueue) - { - if (QueuedTag == Tag) - { - return true; - } - } - - return false; - } - - public long[] GetReleasedBuffers(int Count) - { - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); - - ReleasedCount += ReleasedTagsQueue.Count; - - if (Count > ReleasedCount) - { - Count = ReleasedCount; - } - - List Tags = new List(); - - while (Count-- > 0 && ReleasedTagsQueue.TryDequeue(out long Tag)) - { - Tags.Add(Tag); - } - - while (Count-- > 0 && QueuedTagsQueue.TryDequeue(out long Tag)) - { - AL.SourceUnqueueBuffers(SourceId, 1); - - Tags.Add(Tag); - } - - return Tags.ToArray(); - } - - public int AppendBuffer(long Tag) - { - if (Disposed) - { - throw new ObjectDisposedException(nameof(Track)); - } - - int Id = AL.GenBuffer(); - - Buffers.AddOrUpdate(Tag, Id, (Key, OldId) => - { - AL.DeleteBuffer(OldId); - - return Id; - }); - - QueuedTagsQueue.Enqueue(Tag); - - return Id; - } - - public void CallReleaseCallbackIfNeeded() - { - AL.GetSource(SourceId, ALGetSourcei.BuffersProcessed, out int ReleasedCount); - - if (ReleasedCount > 0) - { - // If we signal, then we also need to have released buffers available - // to return when GetReleasedBuffers is called. - // If playback needs to be re-started due to all buffers being processed, - // then OpenAL zeros the counts (ReleasedCount), so we keep it on the queue. - while (ReleasedCount-- > 0 && QueuedTagsQueue.TryDequeue(out long Tag)) - { - AL.SourceUnqueueBuffers(SourceId, 1); - - ReleasedTagsQueue.Enqueue(Tag); - } - - Callback(); - } - } - - public void Dispose() - { - Dispose(true); - } - - protected virtual void Dispose(bool Disposing) - { - if (Disposing && !Disposed) - { - Disposed = true; - - AL.DeleteSource(SourceId); - - foreach (int Id in Buffers.Values) - { - AL.DeleteBuffer(Id); - } - } - } - } - - private ConcurrentDictionary Tracks; - - private Thread AudioPollerThread; - - private bool KeepPolling; + /// + /// The audio context + /// + private AudioContext _context; - public OpenALAudioOut() - { - Context = new AudioContext(); + /// + /// An object pool containing objects + /// + private ConcurrentDictionary _tracks; - Tracks = new ConcurrentDictionary(); + /// + /// True if the thread need to keep polling + /// + private bool _keepPolling; - KeepPolling = true; + /// + /// The poller thread audio context + /// + private Thread _audioPollerThread; - AudioPollerThread = new Thread(AudioPollerWork); + /// + /// The volume of audio renderer + /// + private float _volume = 1.0f; - AudioPollerThread.Start(); - } + /// + /// True if the volume of audio renderer have changed + /// + private bool _volumeChanged; /// - /// True if OpenAL is supported on the device. + /// True if OpenAL is supported on the device /// public static bool IsSupported { @@ -197,157 +65,232 @@ namespace Ryujinx.Audio } } + public OpenALAudioOut() + { + _context = new AudioContext(); + _tracks = new ConcurrentDictionary(); + _keepPolling = true; + _audioPollerThread = new Thread(AudioPollerWork); + + _audioPollerThread.Start(); + } + private void AudioPollerWork() { do { - foreach (Track Td in Tracks.Values) + foreach (OpenALAudioTrack track in _tracks.Values) { - lock (Td) + lock (track) { - Td.CallReleaseCallbackIfNeeded(); + track.CallReleaseCallbackIfNeeded(); } } // If it's not slept it will waste cycles. Thread.Sleep(10); } - while (KeepPolling); + while (_keepPolling); - foreach (Track Td in Tracks.Values) + foreach (OpenALAudioTrack track in _tracks.Values) { - Td.Dispose(); + track.Dispose(); } - Tracks.Clear(); + _tracks.Clear(); } - public int OpenTrack(int SampleRate, int Channels, ReleaseCallback Callback) + /// + /// Creates a new audio track with the specified parameters + /// + /// The requested sample rate + /// The requested channels + /// A that represents the delegate to invoke when a buffer has been released by the audio track + public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) { - Track Td = new Track(SampleRate, GetALFormat(Channels), Callback); + OpenALAudioTrack track = new OpenALAudioTrack(sampleRate, GetALFormat(channels), callback); - for (int Id = 0; Id < MaxTracks; Id++) + for (int id = 0; id < MaxTracks; id++) { - if (Tracks.TryAdd(Id, Td)) + if (_tracks.TryAdd(id, track)) { - return Id; + return id; } } return -1; } - private ALFormat GetALFormat(int Channels) + private ALFormat GetALFormat(int channels) { - switch (Channels) + switch (channels) { case 1: return ALFormat.Mono16; case 2: return ALFormat.Stereo16; case 6: return ALFormat.Multi51Chn16Ext; } - throw new ArgumentOutOfRangeException(nameof(Channels)); + throw new ArgumentOutOfRangeException(nameof(channels)); } - public void CloseTrack(int Track) + /// + /// Stops playback and closes the track specified by + /// + /// The ID of the track to close + public void CloseTrack(int trackId) { - if (Tracks.TryRemove(Track, out Track Td)) + if (_tracks.TryRemove(trackId, out OpenALAudioTrack track)) { - lock (Td) + lock (track) { - Td.Dispose(); + track.Dispose(); } } } - public bool ContainsBuffer(int Track, long Tag) + /// + /// Returns a value indicating whether the specified buffer is currently reserved by the specified track + /// + /// The track to check + /// The buffer tag to check + public bool ContainsBuffer(int trackId, long bufferTag) { - if (Tracks.TryGetValue(Track, out Track Td)) + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) { - lock (Td) + lock (track) { - return Td.ContainsBuffer(Tag); + return track.ContainsBuffer(bufferTag); } } return false; } - public long[] GetReleasedBuffers(int Track, int MaxCount) + /// + /// Gets a list of buffer tags the specified track is no longer reserving + /// + /// The track to retrieve buffer tags from + /// The maximum amount of buffer tags to retrieve + /// Buffers released by the specified track + public long[] GetReleasedBuffers(int trackId, int maxCount) { - if (Tracks.TryGetValue(Track, out Track Td)) + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) { - lock (Td) + lock (track) { - return Td.GetReleasedBuffers(MaxCount); + return track.GetReleasedBuffers(maxCount); } } return null; } - public void AppendBuffer(int Track, long Tag, T[] Buffer) where T : struct + /// + /// Appends an audio buffer to the specified track + /// + /// The sample type of the buffer + /// The track to append the buffer to + /// The internal tag of the buffer + /// The buffer to append to the track + public void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct { - if (Tracks.TryGetValue(Track, out Track Td)) + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) { - lock (Td) + lock (track) { - int BufferId = Td.AppendBuffer(Tag); + int bufferId = track.AppendBuffer(bufferTag); - int Size = Buffer.Length * Marshal.SizeOf(); + int size = buffer.Length * Marshal.SizeOf(); - AL.BufferData(BufferId, Td.Format, Buffer, Size, Td.SampleRate); + AL.BufferData(bufferId, track.Format, buffer, size, track.SampleRate); - AL.SourceQueueBuffer(Td.SourceId, BufferId); + AL.SourceQueueBuffer(track.SourceId, bufferId); - StartPlaybackIfNeeded(Td); + StartPlaybackIfNeeded(track); } } } - public void Start(int Track) + /// + /// Starts playback + /// + /// The ID of the track to start playback on + public void Start(int trackId) { - if (Tracks.TryGetValue(Track, out Track Td)) + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) { - lock (Td) + lock (track) { - Td.State = PlaybackState.Playing; + track.State = PlaybackState.Playing; - StartPlaybackIfNeeded(Td); + StartPlaybackIfNeeded(track); } } } - private void StartPlaybackIfNeeded(Track Td) + private void StartPlaybackIfNeeded(OpenALAudioTrack track) { - AL.GetSource(Td.SourceId, ALGetSourcei.SourceState, out int StateInt); + AL.GetSource(track.SourceId, ALGetSourcei.SourceState, out int stateInt); - ALSourceState State = (ALSourceState)StateInt; + ALSourceState State = (ALSourceState)stateInt; - if (State != ALSourceState.Playing && Td.State == PlaybackState.Playing) + if (State != ALSourceState.Playing && track.State == PlaybackState.Playing) { - AL.SourcePlay(Td.SourceId); + if (_volumeChanged) + { + AL.Source(track.SourceId, ALSourcef.Gain, _volume); + + _volumeChanged = false; + } + + AL.SourcePlay(track.SourceId); } } - public void Stop(int Track) + /// + /// Stops playback + /// + /// The ID of the track to stop playback on + public void Stop(int trackId) { - if (Tracks.TryGetValue(Track, out Track Td)) + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) { - lock (Td) + lock (track) { - Td.State = PlaybackState.Stopped; + track.State = PlaybackState.Stopped; - AL.SourceStop(Td.SourceId); + AL.SourceStop(track.SourceId); } } } - public PlaybackState GetState(int Track) + /// + /// Get playback volume + /// + public float GetVolume() => _volume; + + /// + /// Set playback volume + /// + /// The volume of the playback + public void SetVolume(float volume) + { + if (!_volumeChanged) + { + _volume = volume; + _volumeChanged = true; + } + } + + /// + /// Gets the current playback state of the specified track + /// + /// The track to retrieve the playback state for + public PlaybackState GetState(int trackId) { - if (Tracks.TryGetValue(Track, out Track Td)) + if (_tracks.TryGetValue(trackId, out OpenALAudioTrack track)) { - return Td.State; + return track.State; } return PlaybackState.Stopped; @@ -358,11 +301,11 @@ namespace Ryujinx.Audio Dispose(true); } - protected virtual void Dispose(bool Disposing) + protected virtual void Dispose(bool disposing) { - if (Disposing) + if (disposing) { - KeepPolling = false; + _keepPolling = false; } } } -- cgit v1.2.3