From c17e1f99f0d9047681ead1d51ff498cd252c2b3c Mon Sep 17 00:00:00 2001 From: Ac_K Date: Fri, 11 Oct 2019 17:54:29 +0200 Subject: [PATCH] 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/IAalOutput.cs | 5 + Ryujinx.Audio/Renderers/DummyAudioOut.cs | 72 ++-- .../Renderers/OpenAL/OpenALAudioOut.cs | 385 ++++++++---------- .../Renderers/OpenAL/OpenALAudioTrack.cs | 142 +++++++ .../Renderers/SoundIo/SoundIoAudioOut.cs | 177 ++++---- .../Audio/AudioOutManager/IAudioOut.cs | 26 ++ 6 files changed, 482 insertions(+), 325 deletions(-) create mode 100644 Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs diff --git a/Ryujinx.Audio/IAalOutput.cs b/Ryujinx.Audio/IAalOutput.cs index 119fc2342d..489f90028e 100644 --- a/Ryujinx.Audio/IAalOutput.cs +++ b/Ryujinx.Audio/IAalOutput.cs @@ -15,8 +15,13 @@ namespace Ryujinx.Audio void AppendBuffer(int trackId, long bufferTag, T[] buffer) where T : struct; void Start(int trackId); + void Stop(int trackId); + float GetVolume(); + + void SetVolume(float volume); + PlaybackState GetState(int trackId); } } \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/DummyAudioOut.cs b/Ryujinx.Audio/Renderers/DummyAudioOut.cs index 56ae5d4f5f..10943ae62b 100644 --- a/Ryujinx.Audio/Renderers/DummyAudioOut.cs +++ b/Ryujinx.Audio/Renderers/DummyAudioOut.cs @@ -1,4 +1,5 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Collections.Generic; namespace Ryujinx.Audio @@ -8,17 +9,18 @@ namespace Ryujinx.Audio /// public class DummyAudioOut : IAalOutput { - private int lastTrackId = 1; + private int _lastTrackId = 1; + private float _volume = 1.0f; - private ConcurrentQueue m_TrackIds; - private ConcurrentQueue m_Buffers; - private ConcurrentDictionary m_ReleaseCallbacks; + private ConcurrentQueue _trackIds; + private ConcurrentQueue _buffers; + private ConcurrentDictionary _releaseCallbacks; public DummyAudioOut() { - m_Buffers = new ConcurrentQueue(); - m_TrackIds = new ConcurrentQueue(); - m_ReleaseCallbacks = new ConcurrentDictionary(); + _buffers = new ConcurrentQueue(); + _trackIds = new ConcurrentQueue(); + _releaseCallbacks = new ConcurrentDictionary(); } /// @@ -30,38 +32,23 @@ namespace Ryujinx.Audio public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) { - int trackId; - - if (!m_TrackIds.TryDequeue(out trackId)) + if (!_trackIds.TryDequeue(out int trackId)) { - trackId = ++lastTrackId; + trackId = ++_lastTrackId; } - m_ReleaseCallbacks[trackId] = callback; + _releaseCallbacks[trackId] = callback; return trackId; } public void CloseTrack(int trackId) { - m_TrackIds.Enqueue(trackId); - m_ReleaseCallbacks.Remove(trackId, out _); + _trackIds.Enqueue(trackId); + _releaseCallbacks.Remove(trackId, out _); } - public void Start(int trackId) { } - - public void Stop(int trackId) { } - - public void AppendBuffer(int trackID, long bufferTag, T[] buffer) - where T : struct - { - m_Buffers.Enqueue(bufferTag); - - if (m_ReleaseCallbacks.TryGetValue(trackID, out var callback)) - { - callback?.Invoke(); - } - } + public bool ContainsBuffer(int trackID, long bufferTag) => false; public long[] GetReleasedBuffers(int trackId, int maxCount) { @@ -69,7 +56,7 @@ namespace Ryujinx.Audio for (int i = 0; i < maxCount; i++) { - if (!m_Buffers.TryDequeue(out long tag)) + if (!_buffers.TryDequeue(out long tag)) { break; } @@ -80,11 +67,30 @@ namespace Ryujinx.Audio return bufferTags.ToArray(); } - public bool ContainsBuffer(int trackID, long bufferTag) => false; + public void AppendBuffer(int trackID, long bufferTag, T[] buffer) where T : struct + { + _buffers.Enqueue(bufferTag); + + if (_releaseCallbacks.TryGetValue(trackID, out var callback)) + { + callback?.Invoke(); + } + } + + public void Start(int trackId) { } + + public void Stop(int trackId) { } + + public float GetVolume() => _volume; + + public void SetVolume(float volume) + { + _volume = volume; + } public void Dispose() { - m_Buffers.Clear(); + _buffers.Clear(); } } -} +} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioOut.cs index 6e085eed26..69f36a4daf 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; - - public OpenALAudioOut() - { - Context = new AudioContext(); - - Tracks = new ConcurrentDictionary(); - - KeepPolling = true; - - AudioPollerThread = new Thread(AudioPollerWork); - - AudioPollerThread.Start(); - } + /// + /// The audio context + /// + private AudioContext _context; /// - /// True if OpenAL is supported on the device. + /// An object pool containing objects + /// + private ConcurrentDictionary _tracks; + + /// + /// True if the thread need to keep polling + /// + private bool _keepPolling; + + /// + /// The poller thread audio context + /// + private Thread _audioPollerThread; + + /// + /// The volume of audio renderer + /// + private float _volume = 1.0f; + + /// + /// True if the volume of audio renderer have changed + /// + private bool _volumeChanged; + + /// + /// 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 (Tracks.TryGetValue(Track, out Track Td)) + if (!_volumeChanged) { - return Td.State; + _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(trackId, out OpenALAudioTrack track)) + { + 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; } } } diff --git a/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs new file mode 100644 index 0000000000..8629dc969c --- /dev/null +++ b/Ryujinx.Audio/Renderers/OpenAL/OpenALAudioTrack.cs @@ -0,0 +1,142 @@ +using OpenTK.Audio.OpenAL; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; + +namespace Ryujinx.Audio +{ + internal class OpenALAudioTrack : IDisposable + { + public int SourceId { get; private set; } + public int SampleRate { get; private set; } + public ALFormat Format { get; private set; } + public PlaybackState State { get; set; } + + private ReleaseCallback _callback; + + private ConcurrentDictionary _buffers; + + private Queue _queuedTagsQueue; + private Queue _releasedTagsQueue; + + private bool _disposed; + + public OpenALAudioTrack(int sampleRate, ALFormat format, ReleaseCallback callback) + { + SampleRate = sampleRate; + Format = format; + State = PlaybackState.Stopped; + SourceId = AL.GenSource(); + + _callback = callback; + + _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(GetType().Name); + } + + 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); + } + } + } + } +} \ No newline at end of file diff --git a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs index f5e4c57d9e..1e487a6d93 100644 --- a/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs +++ b/Ryujinx.Audio/Renderers/SoundIo/SoundIoAudioOut.cs @@ -1,5 +1,6 @@ using Ryujinx.Audio.SoundIo; using SoundIOSharp; +using System; using System.Collections.Generic; namespace Ryujinx.Audio @@ -14,23 +15,33 @@ namespace Ryujinx.Audio /// private const int MaximumTracks = 256; + /// + /// The volume of audio renderer + /// + private float _volume = 1.0f; + + /// + /// True if the volume of audio renderer have changed + /// + private bool _volumeChanged; + /// /// The audio context /// - private SoundIO m_AudioContext; + private SoundIO _audioContext; /// /// The audio device /// - private SoundIODevice m_AudioDevice; + private SoundIODevice _audioDevice; /// /// An object pool containing objects /// - private SoundIoAudioTrackPool m_TrackPool; + private SoundIoAudioTrackPool _trackPool; /// - /// True if SoundIO is supported on the device. + /// True if SoundIO is supported on the device /// public static bool IsSupported { @@ -45,27 +56,13 @@ namespace Ryujinx.Audio /// public SoundIoAudioOut() { - m_AudioContext = new SoundIO(); + _audioContext = new SoundIO(); - m_AudioContext.Connect(); - m_AudioContext.FlushEvents(); + _audioContext.Connect(); + _audioContext.FlushEvents(); - m_AudioDevice = FindNonRawDefaultAudioDevice(m_AudioContext, true); - m_TrackPool = new SoundIoAudioTrackPool(m_AudioContext, m_AudioDevice, MaximumTracks); - } - - /// - /// Gets the current playback state of the specified track - /// - /// The track to retrieve the playback state for - public PlaybackState GetState(int trackId) - { - if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - return track.State; - } - - return PlaybackState.Stopped; + _audioDevice = FindNonRawDefaultAudioDevice(_audioContext, true); + _trackPool = new SoundIoAudioTrackPool(_audioContext, _audioDevice, MaximumTracks); } /// @@ -77,7 +74,7 @@ namespace Ryujinx.Audio /// The created track's Track ID public int OpenTrack(int sampleRate, int channels, ReleaseCallback callback) { - if (!m_TrackPool.TryGet(out SoundIoAudioTrack track)) + if (!_trackPool.TryGet(out SoundIoAudioTrack track)) { return -1; } @@ -94,53 +91,13 @@ namespace Ryujinx.Audio /// The ID of the track to close public void CloseTrack(int trackId) { - if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) { // Close and dispose of the track track.Close(); // Recycle the track back into the pool - m_TrackPool.Put(track); - } - } - - /// - /// Starts playback - /// - /// The ID of the track to start playback on - public void Start(int trackId) - { - if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - track.Start(); - } - } - - /// - /// Stops playback - /// - /// The ID of the track to stop playback on - public void Stop(int trackId) - { - if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - track.Stop(); - } - } - - /// - /// 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 (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) - { - track.AppendBuffer(bufferTag, buffer); + _trackPool.Put(track); } } @@ -151,7 +108,7 @@ namespace Ryujinx.Audio /// The buffer tag to check public bool ContainsBuffer(int trackId, long bufferTag) { - if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) { return track.ContainsBuffer(bufferTag); } @@ -167,7 +124,7 @@ namespace Ryujinx.Audio /// Buffers released by the specified track public long[] GetReleasedBuffers(int trackId, int maxCount) { - if (m_TrackPool.TryGet(trackId, out SoundIoAudioTrack track)) + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) { List bufferTags = new List(); @@ -182,14 +139,92 @@ namespace Ryujinx.Audio return new long[0]; } + /// + /// 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 (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + if (_volumeChanged) + { + track.AudioStream.SetVolume(_volume); + + _volumeChanged = false; + } + + track.AppendBuffer(bufferTag, buffer); + } + } + + /// + /// Starts playback + /// + /// The ID of the track to start playback on + public void Start(int trackId) + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + track.Start(); + } + } + + /// + /// Stops playback + /// + /// The ID of the track to stop playback on + public void Stop(int trackId) + { + if (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + track.Stop(); + } + } + + /// + /// 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 (_trackPool.TryGet(trackId, out SoundIoAudioTrack track)) + { + return track.State; + } + + return PlaybackState.Stopped; + } + /// /// Releases the unmanaged resources used by the /// public void Dispose() { - m_TrackPool.Dispose(); - m_AudioContext.Disconnect(); - m_AudioContext.Dispose(); + _trackPool.Dispose(); + _audioContext.Disconnect(); + _audioContext.Dispose(); } /// diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs index 5b6983d651..11d8036cd1 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioOutManager/IAudioOut.cs @@ -147,6 +147,32 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioOutManager return ResultCode.Success; } + [Command(12)] // 6.0.0+ + // SetAudioOutVolume(s32) + public ResultCode SetAudioOutVolume(ServiceCtx context) + { + // Games send a gain value here, so we need to apply it on the current volume value. + + float gain = context.RequestData.ReadSingle(); + float currentVolume = _audioOut.GetVolume(); + float newVolume = Math.Clamp(currentVolume + gain, 0.0f, 1.0f); + + _audioOut.SetVolume(newVolume); + + return ResultCode.Success; + } + + [Command(13)] // 6.0.0+ + // GetAudioOutVolume() -> s32 + public ResultCode GetAudioOutVolume(ServiceCtx context) + { + float volume = _audioOut.GetVolume(); + + context.ResponseData.Write(volume); + + return ResultCode.Success; + } + public void Dispose() { Dispose(true);