using OpenTK.Audio.OpenAL; using Ryujinx.Audio.Backends.Common; using Ryujinx.Audio.Common; using Ryujinx.Memory; using System; using System.Collections.Generic; using System.Diagnostics; namespace Ryujinx.Audio.Backends.OpenAL { class OpenALHardwareDeviceSession : HardwareDeviceSessionOutputBase { private readonly OpenALHardwareDeviceDriver _driver; private readonly int _sourceId; private readonly ALFormat _targetFormat; private bool _isActive; private readonly Queue _queuedBuffers; private ulong _playedSampleCount; private readonly object _lock = new(); public OpenALHardwareDeviceSession(OpenALHardwareDeviceDriver driver, IVirtualMemoryManager memoryManager, SampleFormat requestedSampleFormat, uint requestedSampleRate, uint requestedChannelCount, float requestedVolume) : base(memoryManager, requestedSampleFormat, requestedSampleRate, requestedChannelCount) { _driver = driver; _queuedBuffers = new Queue(); _sourceId = AL.GenSource(); _targetFormat = GetALFormat(); _isActive = false; _playedSampleCount = 0; SetVolume(requestedVolume); } private ALFormat GetALFormat() { return RequestedSampleFormat switch { SampleFormat.PcmInt16 => RequestedChannelCount switch { 1 => ALFormat.Mono16, 2 => ALFormat.Stereo16, 6 => ALFormat.Multi51Chn16Ext, _ => throw new NotImplementedException($"Unsupported channel config {RequestedChannelCount}"), }, _ => throw new NotImplementedException($"Unsupported sample format {RequestedSampleFormat}"), }; } public override void PrepareToClose() { } private void StartIfNotPlaying() { AL.GetSource(_sourceId, ALGetSourcei.SourceState, out int stateInt); ALSourceState State = (ALSourceState)stateInt; if (State != ALSourceState.Playing) { AL.SourcePlay(_sourceId); } } public override void QueueBuffer(AudioBuffer buffer) { lock (_lock) { OpenALAudioBuffer driverBuffer = new() { DriverIdentifier = buffer.DataPointer, BufferId = AL.GenBuffer(), SampleCount = GetSampleCount(buffer), }; AL.BufferData(driverBuffer.BufferId, _targetFormat, buffer.Data, (int)RequestedSampleRate); _queuedBuffers.Enqueue(driverBuffer); AL.SourceQueueBuffer(_sourceId, driverBuffer.BufferId); if (_isActive) { StartIfNotPlaying(); } } } public override void SetVolume(float volume) { lock (_lock) { AL.Source(_sourceId, ALSourcef.Gain, volume); } } public override float GetVolume() { AL.GetSource(_sourceId, ALSourcef.Gain, out float volume); return volume; } public override void Start() { lock (_lock) { _isActive = true; StartIfNotPlaying(); } } public override void Stop() { lock (_lock) { SetVolume(0.0f); AL.SourceStop(_sourceId); _isActive = false; } } public override void UnregisterBuffer(AudioBuffer buffer) { } public override bool WasBufferFullyConsumed(AudioBuffer buffer) { lock (_lock) { if (!_queuedBuffers.TryPeek(out OpenALAudioBuffer driverBuffer)) { return true; } return driverBuffer.DriverIdentifier != buffer.DataPointer; } } public override ulong GetPlayedSampleCount() { lock (_lock) { return _playedSampleCount; } } public bool Update() { lock (_lock) { if (_isActive) { AL.GetSource(_sourceId, ALGetSourcei.BuffersProcessed, out int releasedCount); if (releasedCount > 0) { int[] bufferIds = new int[releasedCount]; AL.SourceUnqueueBuffers(_sourceId, releasedCount, bufferIds); int i = 0; while (_queuedBuffers.TryPeek(out OpenALAudioBuffer buffer) && i < bufferIds.Length) { if (buffer.BufferId == bufferIds[i]) { _playedSampleCount += buffer.SampleCount; _queuedBuffers.TryDequeue(out _); i++; } } Debug.Assert(i == bufferIds.Length, "Unknown buffer ids found!"); AL.DeleteBuffers(bufferIds); } return releasedCount > 0; } return false; } } protected virtual void Dispose(bool disposing) { if (disposing && _driver.Unregister(this)) { lock (_lock) { PrepareToClose(); Stop(); AL.DeleteSource(_sourceId); } } } public override void Dispose() { Dispose(true); GC.SuppressFinalize(this); } } }