From cee712105850ac3385cd0091a923438167433f9f Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 8 Apr 2023 01:22:00 +0200 Subject: Move solution and projects to src --- src/Ryujinx.Audio/Common/AudioBuffer.cs | 37 ++ src/Ryujinx.Audio/Common/AudioDeviceSession.cs | 518 +++++++++++++++++++++ src/Ryujinx.Audio/Common/AudioDeviceState.cs | 18 + .../Common/AudioInputConfiguration.cs | 29 ++ .../Common/AudioOutputConfiguration.cs | 37 ++ src/Ryujinx.Audio/Common/AudioUserBuffer.cs | 36 ++ src/Ryujinx.Audio/Common/SampleFormat.cs | 43 ++ 7 files changed, 718 insertions(+) create mode 100644 src/Ryujinx.Audio/Common/AudioBuffer.cs create mode 100644 src/Ryujinx.Audio/Common/AudioDeviceSession.cs create mode 100644 src/Ryujinx.Audio/Common/AudioDeviceState.cs create mode 100644 src/Ryujinx.Audio/Common/AudioInputConfiguration.cs create mode 100644 src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs create mode 100644 src/Ryujinx.Audio/Common/AudioUserBuffer.cs create mode 100644 src/Ryujinx.Audio/Common/SampleFormat.cs (limited to 'src/Ryujinx.Audio/Common') diff --git a/src/Ryujinx.Audio/Common/AudioBuffer.cs b/src/Ryujinx.Audio/Common/AudioBuffer.cs new file mode 100644 index 00000000..b79401b7 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioBuffer.cs @@ -0,0 +1,37 @@ +using Ryujinx.Audio.Integration; + +namespace Ryujinx.Audio.Common +{ + /// + /// Represent an audio buffer that will be used by an . + /// + public class AudioBuffer + { + /// + /// Unique tag of this buffer. + /// + /// Unique per session + public ulong BufferTag; + + /// + /// Pointer to the user samples. + /// + public ulong DataPointer; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// The timestamp at which the buffer was played. + /// + /// Not used but useful for debugging + public ulong PlayedTimestamp; + + /// + /// The user samples. + /// + public byte[] Data; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioDeviceSession.cs b/src/Ryujinx.Audio/Common/AudioDeviceSession.cs new file mode 100644 index 00000000..0191f7cc --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioDeviceSession.cs @@ -0,0 +1,518 @@ +using Ryujinx.Audio.Integration; +using Ryujinx.Common; +using System; +using System.Diagnostics; + +namespace Ryujinx.Audio.Common +{ + /// + /// An audio device session. + /// + class AudioDeviceSession : IDisposable + { + /// + /// The volume of the . + /// + private float _volume; + + /// + /// The state of the . + /// + private AudioDeviceState _state; + + /// + /// Array of all buffers currently used or released. + /// + private AudioBuffer[] _buffers; + + /// + /// The server index inside (appended but not queued to device driver). + /// + private uint _serverBufferIndex; + + /// + /// The hardware index inside (queued to device driver). + /// + private uint _hardwareBufferIndex; + + /// + /// The released index inside (released by the device driver). + /// + private uint _releasedBufferIndex; + + /// + /// The count of buffer appended (server side). + /// + private uint _bufferAppendedCount; + + /// + /// The count of buffer registered (driver side). + /// + private uint _bufferRegisteredCount; + + /// + /// The count of buffer released (released by the driver side). + /// + private uint _bufferReleasedCount; + + /// + /// The released buffer event. + /// + private IWritableEvent _bufferEvent; + + /// + /// The session on the device driver. + /// + private IHardwareDeviceSession _hardwareDeviceSession; + + /// + /// Max number of buffers that can be registered to the device driver at a time. + /// + private uint _bufferRegisteredLimit; + + /// + /// Create a new . + /// + /// The device driver session associated + /// The release buffer event + /// The max number of buffers that can be registered to the device driver at a time + public AudioDeviceSession(IHardwareDeviceSession deviceSession, IWritableEvent bufferEvent, uint bufferRegisteredLimit = 4) + { + _bufferEvent = bufferEvent; + _hardwareDeviceSession = deviceSession; + _bufferRegisteredLimit = bufferRegisteredLimit; + + _buffers = new AudioBuffer[Constants.AudioDeviceBufferCountMax]; + _serverBufferIndex = 0; + _hardwareBufferIndex = 0; + _releasedBufferIndex = 0; + + _bufferAppendedCount = 0; + _bufferRegisteredCount = 0; + _bufferReleasedCount = 0; + _volume = deviceSession.GetVolume(); + _state = AudioDeviceState.Stopped; + } + + /// + /// Get the released buffer event. + /// + /// The released buffer event + public IWritableEvent GetBufferEvent() + { + return _bufferEvent; + } + + /// + /// Get the state of the session. + /// + /// The state of the session + public AudioDeviceState GetState() + { + Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped); + + return _state; + } + + /// + /// Get the total buffer count (server + driver + released). + /// + /// Return the total buffer count + private uint GetTotalBufferCount() + { + uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount; + + Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax); + + return bufferCount; + } + + /// + /// Register a new on the server side. + /// + /// The to register + /// True if the operation succeeded + private bool RegisterBuffer(AudioBuffer buffer) + { + if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _buffers[_serverBufferIndex] = buffer; + _serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount++; + + return true; + } + + /// + /// Flush server buffers to hardware. + /// + private void FlushToHardware() + { + uint bufferToFlushCount = Math.Min(Math.Min(_bufferAppendedCount, 4), _bufferRegisteredLimit - _bufferRegisteredCount); + + AudioBuffer[] buffersToFlush = new AudioBuffer[bufferToFlushCount]; + + uint hardwareBufferIndex = _hardwareBufferIndex; + + for (int i = 0; i < buffersToFlush.Length; i++) + { + buffersToFlush[i] = _buffers[hardwareBufferIndex]; + + _bufferAppendedCount--; + _bufferRegisteredCount++; + + hardwareBufferIndex = (hardwareBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + _hardwareBufferIndex = hardwareBufferIndex; + + for (int i = 0; i < buffersToFlush.Length; i++) + { + _hardwareDeviceSession.QueueBuffer(buffersToFlush[i]); + } + } + + /// + /// Get the current index of the playing on the driver side. + /// + /// The output index of the playing on the driver side + /// True if any buffer is playing + private bool TryGetPlayingBufferIndex(out uint playingIndex) + { + if (_bufferRegisteredCount > 0) + { + playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + return true; + } + + playingIndex = 0; + + return false; + } + + /// + /// Try to pop the playing on the driver side. + /// + /// The output playing on the driver side + /// True if any buffer is playing + private bool TryPopPlayingBuffer(out AudioBuffer buffer) + { + if (_bufferRegisteredCount > 0) + { + uint bufferIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + buffer = _buffers[bufferIndex]; + + _buffers[bufferIndex] = null; + + _bufferRegisteredCount--; + + return true; + } + + buffer = null; + + return false; + } + + /// + /// Try to pop a released by the driver side. + /// + /// The output released by the driver side + /// True if any buffer has been released + public bool TryPopReleasedBuffer(out AudioBuffer buffer) + { + if (_bufferReleasedCount > 0) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + buffer = _buffers[bufferIndex]; + + _buffers[bufferIndex] = null; + + _bufferReleasedCount--; + + return true; + } + + buffer = null; + + return false; + } + + /// + /// Release a . + /// + /// The to release + private void ReleaseBuffer(AudioBuffer buffer) + { + buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds; + + _bufferRegisteredCount--; + _bufferReleasedCount++; + + _releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + /// + /// Update the released buffers. + /// + /// True if the session is currently stopping + private void UpdateReleaseBuffers(bool updateForStop = false) + { + bool wasAnyBuffersReleased = false; + + while (TryGetPlayingBufferIndex(out uint playingIndex)) + { + if (!updateForStop && !_hardwareDeviceSession.WasBufferFullyConsumed(_buffers[playingIndex])) + { + break; + } + + if (updateForStop) + { + _hardwareDeviceSession.UnregisterBuffer(_buffers[playingIndex]); + } + + ReleaseBuffer(_buffers[playingIndex]); + + wasAnyBuffersReleased = true; + } + + if (wasAnyBuffersReleased) + { + _bufferEvent.Signal(); + } + } + + /// + /// Append a new . + /// + /// The to append + /// True if the buffer was appended + public bool AppendBuffer(AudioBuffer buffer) + { + if (_hardwareDeviceSession.RegisterBuffer(buffer)) + { + if (RegisterBuffer(buffer)) + { + FlushToHardware(); + + return true; + } + + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + return false; + } + + public bool AppendUacBuffer(AudioBuffer buffer, uint handle) + { + // NOTE: On hardware, there is another RegisterBuffer method taking an handle. + // This variant of the call always return false (stubbed?) as a result this logic will never succeed. + + return false; + } + + /// + /// Start the audio session. + /// + /// A reporting an error or a success + public ResultCode Start() + { + if (_state == AudioDeviceState.Started) + { + return ResultCode.OperationFailed; + } + + _hardwareDeviceSession.Start(); + + _state = AudioDeviceState.Started; + + FlushToHardware(); + + _hardwareDeviceSession.SetVolume(_volume); + + return ResultCode.Success; + } + + /// + /// Stop the audio session. + /// + /// A reporting an error or a success + public ResultCode Stop() + { + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.Stop(); + + UpdateReleaseBuffers(true); + + _state = AudioDeviceState.Stopped; + } + + return ResultCode.Success; + } + + /// + /// Get the volume of the session. + /// + /// The volume of the session + public float GetVolume() + { + return _hardwareDeviceSession.GetVolume(); + } + + /// + /// Set the volume of the session. + /// + /// The new volume to set + public void SetVolume(float volume) + { + _volume = volume; + + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.SetVolume(volume); + } + } + + /// + /// Get the count of buffer currently in use (server + driver side). + /// + /// The count of buffer currently in use + public uint GetBufferCount() + { + return _bufferAppendedCount + _bufferRegisteredCount; + } + + /// + /// Check if a buffer is present. + /// + /// The unique tag of the buffer + /// Return true if a buffer is present + public bool ContainsBuffer(ulong bufferTag) + { + uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax; + + uint totalBufferCount = GetTotalBufferCount(); + + for (int i = 0; i < totalBufferCount; i++) + { + if (_buffers[bufferIndex].BufferTag == bufferTag) + { + return true; + } + + bufferIndex = (bufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + return false; + } + + /// + /// Get the count of sample played in this session. + /// + /// The count of sample played in this session + public ulong GetPlayedSampleCount() + { + if (_state == AudioDeviceState.Stopped) + { + return 0; + } + else + { + return _hardwareDeviceSession.GetPlayedSampleCount(); + } + } + + /// + /// Flush all buffers to the initial state. + /// + /// True if any buffer was flushed + public bool FlushBuffers() + { + if (_state == AudioDeviceState.Stopped) + { + return false; + } + + uint bufferCount = GetBufferCount(); + + while (TryPopReleasedBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + while (TryPopPlayingBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + if (_bufferRegisteredCount == 0 || (_bufferReleasedCount + _bufferAppendedCount) > Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _bufferReleasedCount += _bufferAppendedCount; + _releasedBufferIndex = (_releasedBufferIndex + _bufferAppendedCount) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount = 0; + _hardwareBufferIndex = _serverBufferIndex; + + if (bufferCount > 0) + { + _bufferEvent.Signal(); + } + + return true; + } + + /// + /// Update the session. + /// + public void Update() + { + if (_state == AudioDeviceState.Started) + { + UpdateReleaseBuffers(); + FlushToHardware(); + } + } + + public void Dispose() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Tell the hardware session that we are ending. + _hardwareDeviceSession.PrepareToClose(); + + // Unregister all buffers + + while (TryPopReleasedBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + while (TryPopPlayingBuffer(out AudioBuffer buffer)) + { + _hardwareDeviceSession.UnregisterBuffer(buffer); + } + + // Finally dispose hardware session. + _hardwareDeviceSession.Dispose(); + + _bufferEvent.Signal(); + } + } + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioDeviceState.cs b/src/Ryujinx.Audio/Common/AudioDeviceState.cs new file mode 100644 index 00000000..b3f968da --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioDeviceState.cs @@ -0,0 +1,18 @@ +namespace Ryujinx.Audio.Common +{ + /// + /// Audio device state. + /// + public enum AudioDeviceState : uint + { + /// + /// The audio device is started. + /// + Started, + + /// + /// The audio device is stopped. + /// + Stopped + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs b/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs new file mode 100644 index 00000000..d3cfdd47 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioInputConfiguration.cs @@ -0,0 +1,29 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user input configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioInputConfiguration + { + /// + /// The target sample rate of the user. + /// + /// Only 48000Hz is considered valid, other sample rates will be refused. + public uint SampleRate; + + /// + /// The target channel count of the user. + /// + /// Only Stereo and Surround are considered valid, other configurations will be refused. + /// Not used in audin. + public ushort ChannelCount; + + /// + /// Reserved/unused. + /// + private ushort _reserved; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs b/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs new file mode 100644 index 00000000..e17e1757 --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs @@ -0,0 +1,37 @@ +using Ryujinx.Common.Memory; +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio system output configuration. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioOutputConfiguration + { + /// + /// The target sample rate of the system. + /// + public uint SampleRate; + + /// + /// The target channel count of the system. + /// + public uint ChannelCount; + + /// + /// Reserved/unused + /// + public SampleFormat SampleFormat; + + /// + /// Reserved/unused. + /// + private Array3 _padding; + + /// + /// The initial audio system state. + /// + public AudioDeviceState AudioOutState; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/AudioUserBuffer.cs b/src/Ryujinx.Audio/Common/AudioUserBuffer.cs new file mode 100644 index 00000000..50ab67fa --- /dev/null +++ b/src/Ryujinx.Audio/Common/AudioUserBuffer.cs @@ -0,0 +1,36 @@ +using System.Runtime.InteropServices; + +namespace Ryujinx.Audio.Common +{ + /// + /// Audio user buffer. + /// + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioUserBuffer + { + /// + /// Pointer to the next buffer (ignored). + /// + public ulong NextBuffer; + + /// + /// Pointer to the user samples. + /// + public ulong Data; + + /// + /// Capacity of the buffer (unused). + /// + public ulong Capacity; + + /// + /// Size of the user samples region. + /// + public ulong DataSize; + + /// + /// Offset in the user samples region (unused). + /// + public ulong DataOffset; + } +} \ No newline at end of file diff --git a/src/Ryujinx.Audio/Common/SampleFormat.cs b/src/Ryujinx.Audio/Common/SampleFormat.cs new file mode 100644 index 00000000..901410a2 --- /dev/null +++ b/src/Ryujinx.Audio/Common/SampleFormat.cs @@ -0,0 +1,43 @@ +namespace Ryujinx.Audio.Common +{ + /// + /// Sample format definition. + /// + public enum SampleFormat : byte + { + /// + /// Invalid sample format. + /// + Invalid = 0, + + /// + /// PCM8 sample format. (unsupported) + /// + PcmInt8 = 1, + + /// + /// PCM16 sample format. + /// + PcmInt16 = 2, + + /// + /// PCM24 sample format. (unsupported) + /// + PcmInt24 = 3, + + /// + /// PCM32 sample format. + /// + PcmInt32 = 4, + + /// + /// PCM Float sample format. + /// + PcmFloat = 5, + + /// + /// ADPCM sample format. (Also known as GC-ADPCM) + /// + Adpcm = 6 + } +} \ No newline at end of file -- cgit v1.2.3