diff options
author | TSR Berry <20988865+TSRBerry@users.noreply.github.com> | 2023-04-08 01:22:00 +0200 |
---|---|---|
committer | Mary <thog@protonmail.com> | 2023-04-27 23:51:14 +0200 |
commit | cee712105850ac3385cd0091a923438167433f9f (patch) | |
tree | 4a5274b21d8b7f938c0d0ce18736d3f2993b11b1 /src/Ryujinx.Audio/Common | |
parent | cd124bda587ef09668a971fa1cac1c3f0cfc9f21 (diff) |
Move solution and projects to src
Diffstat (limited to 'src/Ryujinx.Audio/Common')
-rw-r--r-- | src/Ryujinx.Audio/Common/AudioBuffer.cs | 37 | ||||
-rw-r--r-- | src/Ryujinx.Audio/Common/AudioDeviceSession.cs | 518 | ||||
-rw-r--r-- | src/Ryujinx.Audio/Common/AudioDeviceState.cs | 18 | ||||
-rw-r--r-- | src/Ryujinx.Audio/Common/AudioInputConfiguration.cs | 29 | ||||
-rw-r--r-- | src/Ryujinx.Audio/Common/AudioOutputConfiguration.cs | 37 | ||||
-rw-r--r-- | src/Ryujinx.Audio/Common/AudioUserBuffer.cs | 36 | ||||
-rw-r--r-- | src/Ryujinx.Audio/Common/SampleFormat.cs | 43 |
7 files changed, 718 insertions, 0 deletions
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 +{ + /// <summary> + /// Represent an audio buffer that will be used by an <see cref="IHardwareDeviceSession"/>. + /// </summary> + public class AudioBuffer + { + /// <summary> + /// Unique tag of this buffer. + /// </summary> + /// <remarks>Unique per session</remarks> + public ulong BufferTag; + + /// <summary> + /// Pointer to the user samples. + /// </summary> + public ulong DataPointer; + + /// <summary> + /// Size of the user samples region. + /// </summary> + public ulong DataSize; + + /// <summary> + /// The timestamp at which the buffer was played. + /// </summary> + /// <remarks>Not used but useful for debugging</remarks> + public ulong PlayedTimestamp; + + /// <summary> + /// The user samples. + /// </summary> + 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 +{ + /// <summary> + /// An audio device session. + /// </summary> + class AudioDeviceSession : IDisposable + { + /// <summary> + /// The volume of the <see cref="AudioDeviceSession"/>. + /// </summary> + private float _volume; + + /// <summary> + /// The state of the <see cref="AudioDeviceSession"/>. + /// </summary> + private AudioDeviceState _state; + + /// <summary> + /// Array of all buffers currently used or released. + /// </summary> + private AudioBuffer[] _buffers; + + /// <summary> + /// The server index inside <see cref="_buffers"/> (appended but not queued to device driver). + /// </summary> + private uint _serverBufferIndex; + + /// <summary> + /// The hardware index inside <see cref="_buffers"/> (queued to device driver). + /// </summary> + private uint _hardwareBufferIndex; + + /// <summary> + /// The released index inside <see cref="_buffers"/> (released by the device driver). + /// </summary> + private uint _releasedBufferIndex; + + /// <summary> + /// The count of buffer appended (server side). + /// </summary> + private uint _bufferAppendedCount; + + /// <summary> + /// The count of buffer registered (driver side). + /// </summary> + private uint _bufferRegisteredCount; + + /// <summary> + /// The count of buffer released (released by the driver side). + /// </summary> + private uint _bufferReleasedCount; + + /// <summary> + /// The released buffer event. + /// </summary> + private IWritableEvent _bufferEvent; + + /// <summary> + /// The session on the device driver. + /// </summary> + private IHardwareDeviceSession _hardwareDeviceSession; + + /// <summary> + /// Max number of buffers that can be registered to the device driver at a time. + /// </summary> + private uint _bufferRegisteredLimit; + + /// <summary> + /// Create a new <see cref="AudioDeviceSession"/>. + /// </summary> + /// <param name="deviceSession">The device driver session associated</param> + /// <param name="bufferEvent">The release buffer event</param> + /// <param name="bufferRegisteredLimit">The max number of buffers that can be registered to the device driver at a time</param> + 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; + } + + /// <summary> + /// Get the released buffer event. + /// </summary> + /// <returns>The released buffer event</returns> + public IWritableEvent GetBufferEvent() + { + return _bufferEvent; + } + + /// <summary> + /// Get the state of the session. + /// </summary> + /// <returns>The state of the session</returns> + public AudioDeviceState GetState() + { + Debug.Assert(_state == AudioDeviceState.Started || _state == AudioDeviceState.Stopped); + + return _state; + } + + /// <summary> + /// Get the total buffer count (server + driver + released). + /// </summary> + /// <returns>Return the total buffer count</returns> + private uint GetTotalBufferCount() + { + uint bufferCount = _bufferAppendedCount + _bufferRegisteredCount + _bufferReleasedCount; + + Debug.Assert(bufferCount <= Constants.AudioDeviceBufferCountMax); + + return bufferCount; + } + + /// <summary> + /// Register a new <see cref="AudioBuffer"/> on the server side. + /// </summary> + /// <param name="buffer">The <see cref="AudioBuffer"/> to register</param> + /// <returns>True if the operation succeeded</returns> + private bool RegisterBuffer(AudioBuffer buffer) + { + if (GetTotalBufferCount() == Constants.AudioDeviceBufferCountMax) + { + return false; + } + + _buffers[_serverBufferIndex] = buffer; + _serverBufferIndex = (_serverBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + _bufferAppendedCount++; + + return true; + } + + /// <summary> + /// Flush server buffers to hardware. + /// </summary> + 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]); + } + } + + /// <summary> + /// Get the current index of the <see cref="AudioBuffer"/> playing on the driver side. + /// </summary> + /// <param name="playingIndex">The output index of the <see cref="AudioBuffer"/> playing on the driver side</param> + /// <returns>True if any buffer is playing</returns> + private bool TryGetPlayingBufferIndex(out uint playingIndex) + { + if (_bufferRegisteredCount > 0) + { + playingIndex = (_hardwareBufferIndex - _bufferRegisteredCount) % Constants.AudioDeviceBufferCountMax; + + return true; + } + + playingIndex = 0; + + return false; + } + + /// <summary> + /// Try to pop the <see cref="AudioBuffer"/> playing on the driver side. + /// </summary> + /// <param name="buffer">The output <see cref="AudioBuffer"/> playing on the driver side</param> + /// <returns>True if any buffer is playing</returns> + 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; + } + + /// <summary> + /// Try to pop a <see cref="AudioBuffer"/> released by the driver side. + /// </summary> + /// <param name="buffer">The output <see cref="AudioBuffer"/> released by the driver side</param> + /// <returns>True if any buffer has been released</returns> + 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; + } + + /// <summary> + /// Release a <see cref="AudioBuffer"/>. + /// </summary> + /// <param name="buffer">The <see cref="AudioBuffer"/> to release</param> + private void ReleaseBuffer(AudioBuffer buffer) + { + buffer.PlayedTimestamp = (ulong)PerformanceCounter.ElapsedNanoseconds; + + _bufferRegisteredCount--; + _bufferReleasedCount++; + + _releasedBufferIndex = (_releasedBufferIndex + 1) % Constants.AudioDeviceBufferCountMax; + } + + /// <summary> + /// Update the released buffers. + /// </summary> + /// <param name="updateForStop">True if the session is currently stopping</param> + 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(); + } + } + + /// <summary> + /// Append a new <see cref="AudioBuffer"/>. + /// </summary> + /// <param name="buffer">The <see cref="AudioBuffer"/> to append</param> + /// <returns>True if the buffer was appended</returns> + 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; + } + + /// <summary> + /// Start the audio session. + /// </summary> + /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns> + public ResultCode Start() + { + if (_state == AudioDeviceState.Started) + { + return ResultCode.OperationFailed; + } + + _hardwareDeviceSession.Start(); + + _state = AudioDeviceState.Started; + + FlushToHardware(); + + _hardwareDeviceSession.SetVolume(_volume); + + return ResultCode.Success; + } + + /// <summary> + /// Stop the audio session. + /// </summary> + /// <returns>A <see cref="ResultCode"/> reporting an error or a success</returns> + public ResultCode Stop() + { + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.Stop(); + + UpdateReleaseBuffers(true); + + _state = AudioDeviceState.Stopped; + } + + return ResultCode.Success; + } + + /// <summary> + /// Get the volume of the session. + /// </summary> + /// <returns>The volume of the session</returns> + public float GetVolume() + { + return _hardwareDeviceSession.GetVolume(); + } + + /// <summary> + /// Set the volume of the session. + /// </summary> + /// <param name="volume">The new volume to set</param> + public void SetVolume(float volume) + { + _volume = volume; + + if (_state == AudioDeviceState.Started) + { + _hardwareDeviceSession.SetVolume(volume); + } + } + + /// <summary> + /// Get the count of buffer currently in use (server + driver side). + /// </summary> + /// <returns>The count of buffer currently in use</returns> + public uint GetBufferCount() + { + return _bufferAppendedCount + _bufferRegisteredCount; + } + + /// <summary> + /// Check if a buffer is present. + /// </summary> + /// <param name="bufferTag">The unique tag of the buffer</param> + /// <returns>Return true if a buffer is present</returns> + 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; + } + + /// <summary> + /// Get the count of sample played in this session. + /// </summary> + /// <returns>The count of sample played in this session</returns> + public ulong GetPlayedSampleCount() + { + if (_state == AudioDeviceState.Stopped) + { + return 0; + } + else + { + return _hardwareDeviceSession.GetPlayedSampleCount(); + } + } + + /// <summary> + /// Flush all buffers to the initial state. + /// </summary> + /// <returns>True if any buffer was flushed</returns> + 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; + } + + /// <summary> + /// Update the session. + /// </summary> + 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 +{ + /// <summary> + /// Audio device state. + /// </summary> + public enum AudioDeviceState : uint + { + /// <summary> + /// The audio device is started. + /// </summary> + Started, + + /// <summary> + /// The audio device is stopped. + /// </summary> + 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 +{ + /// <summary> + /// Audio user input configuration. + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioInputConfiguration + { + /// <summary> + /// The target sample rate of the user. + /// </summary> + /// <remarks>Only 48000Hz is considered valid, other sample rates will be refused.</remarks> + public uint SampleRate; + + /// <summary> + /// The target channel count of the user. + /// </summary> + /// <remarks>Only Stereo and Surround are considered valid, other configurations will be refused.</remarks> + /// <remarks>Not used in audin.</remarks> + public ushort ChannelCount; + + /// <summary> + /// Reserved/unused. + /// </summary> + 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 +{ + /// <summary> + /// Audio system output configuration. + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioOutputConfiguration + { + /// <summary> + /// The target sample rate of the system. + /// </summary> + public uint SampleRate; + + /// <summary> + /// The target channel count of the system. + /// </summary> + public uint ChannelCount; + + /// <summary> + /// Reserved/unused + /// </summary> + public SampleFormat SampleFormat; + + /// <summary> + /// Reserved/unused. + /// </summary> + private Array3<byte> _padding; + + /// <summary> + /// The initial audio system state. + /// </summary> + 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 +{ + /// <summary> + /// Audio user buffer. + /// </summary> + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct AudioUserBuffer + { + /// <summary> + /// Pointer to the next buffer (ignored). + /// </summary> + public ulong NextBuffer; + + /// <summary> + /// Pointer to the user samples. + /// </summary> + public ulong Data; + + /// <summary> + /// Capacity of the buffer (unused). + /// </summary> + public ulong Capacity; + + /// <summary> + /// Size of the user samples region. + /// </summary> + public ulong DataSize; + + /// <summary> + /// Offset in the user samples region (unused). + /// </summary> + 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 +{ + /// <summary> + /// Sample format definition. + /// </summary> + public enum SampleFormat : byte + { + /// <summary> + /// Invalid sample format. + /// </summary> + Invalid = 0, + + /// <summary> + /// PCM8 sample format. (unsupported) + /// </summary> + PcmInt8 = 1, + + /// <summary> + /// PCM16 sample format. + /// </summary> + PcmInt16 = 2, + + /// <summary> + /// PCM24 sample format. (unsupported) + /// </summary> + PcmInt24 = 3, + + /// <summary> + /// PCM32 sample format. + /// </summary> + PcmInt32 = 4, + + /// <summary> + /// PCM Float sample format. + /// </summary> + PcmFloat = 5, + + /// <summary> + /// ADPCM sample format. (Also known as GC-ADPCM) + /// </summary> + Adpcm = 6 + } +}
\ No newline at end of file |