diff options
author | Mary <me@thog.eu> | 2021-02-26 01:11:56 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-26 01:11:56 +0100 |
commit | f556c80d0230056335632b60c71f1567e177239e (patch) | |
tree | 748aa6be62b93a8e941e25dbd83f39e1dbb37035 /Ryujinx.Audio/Common/AudioDeviceSession.cs | |
parent | 1c49089ff00fc87dc4872f135dc6a0d36169a970 (diff) |
Haydn: Part 1 (#2007)
* Haydn: Part 1
Based on my reverse of audio 11.0.0.
As always, core implementation under LGPLv3 for the same reasons as for Amadeus.
This place the bases of a more flexible audio system while making audout & audin accurate.
This have the following improvements:
- Complete reimplementation of audout and audin.
- Audin currently only have a dummy backend.
- Dramatically reduce CPU usage by up to 50% in common cases (SoundIO and OpenAL).
- Audio Renderer now can output to 5.1 devices when supported.
- Audio Renderer init its backend on demand instead of keeping two up all the time.
- All backends implementation are now in their own project.
- Ryujinx.Audio.Renderer was renamed Ryujinx.Audio and was refactored because of this.
As a note, games having issues with OpenAL haven't improved and will not
because of OpenAL design (stopping when buffers finish playing causing
possible audio "pops" when buffers are very small).
* Update for latest hexkyz's edits on Switchbrew
* audren: Rollback channel configuration changes
* Address gdkchan's comments
* Fix typo in OpenAL backend driver
* Address last comments
* Fix a nit
* Address gdkchan's comments
Diffstat (limited to 'Ryujinx.Audio/Common/AudioDeviceSession.cs')
-rw-r--r-- | Ryujinx.Audio/Common/AudioDeviceSession.cs | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/Ryujinx.Audio/Common/AudioDeviceSession.cs b/Ryujinx.Audio/Common/AudioDeviceSession.cs new file mode 100644 index 00000000..fbdb5a75 --- /dev/null +++ b/Ryujinx.Audio/Common/AudioDeviceSession.cs @@ -0,0 +1,532 @@ +// Copyright (c) 2019-2021 Ryujinx +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. +// + +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 = 1.0f; + _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; + + for (int i = 0; i < GetTotalBufferCount(); 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(); + } + } + } +} |