forked from MeloNX/MeloNX
This driver runs ahead by the given duration (measured in samples at 48000hz). For games that rely on buffer release feedback to know when to provide audio frames, this can trick them into running with a higher audio latency on platforms that require it to avoid stuttering. See `Switch.cs` for the sampleDelay definition. It's currently 0, though in future it could be configurable or switch on automatically for certain configurations. For audio sources that _do not_ care about sample release timing, such as the current AudioRenderer, this will not change their behaviour.
207 lines
5.9 KiB
C#
207 lines
5.9 KiB
C#
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<OpenALAudioBuffer> _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<OpenALAudioBuffer>();
|
|
_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.HostTag,
|
|
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.HostTag;
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
}
|