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(); } } } }