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