Expose experimental WASAPI support as a user config toggle

This commit is contained in:
Dean Herbert
2025-10-25 13:10:38 +09:00
parent 496247f349
commit d97d05191d
5 changed files with 28 additions and 15 deletions

View File

@@ -96,6 +96,13 @@ namespace osu.Framework.Audio
/// </summary>
public readonly Bindable<string> AudioDevice = new Bindable<string>();
/// <summary>
/// Whether to use experimental WASAPI initialisation on windows.
/// This generally results in lower audio latency, but also changes the audio synchronisation from
/// historical expectations, meaning users / application will have to account for different offsets.
/// </summary>
public readonly BindableBool UseExperimentalWasapi = new BindableBool();
/// <summary>
/// Volume of all samples played game-wide.
/// </summary>
@@ -176,6 +183,7 @@ namespace osu.Framework.Audio
thread.RegisterManager(this);
AudioDevice.ValueChanged += _ => onDeviceChanged();
UseExperimentalWasapi.ValueChanged += _ => onDeviceChanged();
GlobalMixerHandle.ValueChanged += handle =>
{
onDeviceChanged();
@@ -430,10 +438,16 @@ namespace osu.Framework.Audio
// See https://www.un4seen.com/forum/?topic=19601 for more information.
Bass.Configure((ManagedBass.Configuration)70, false);
if (!thread.InitDevice(device))
return false;
if (UseExperimentalWasapi.Value)
{
if (thread.InitDevice(device, true))
return true;
return true;
Logger.Log($"BASS device {device} failed to initialise with experimental WASAPI, disabling", level: LogLevel.Error);
UseExperimentalWasapi.Value = false;
}
return thread.InitDevice(device, false);
}
private void syncAudioDevices()

View File

@@ -32,6 +32,7 @@ namespace osu.Framework.Configuration
SetDefault(FrameworkSetting.WindowedPositionY, 0.5, -0.5, 1.5);
SetDefault(FrameworkSetting.LastDisplayDevice, DisplayIndex.Default);
SetDefault(FrameworkSetting.AudioDevice, string.Empty);
SetDefault(FrameworkSetting.AudioUseExperimentalWasapi, false);
SetDefault(FrameworkSetting.VolumeUniversal, 1.0, 0.0, 1.0, 0.01);
SetDefault(FrameworkSetting.VolumeMusic, 1.0, 0.0, 1.0, 0.01);
SetDefault(FrameworkSetting.VolumeEffect, 1.0, 0.0, 1.0, 0.01);
@@ -79,6 +80,7 @@ namespace osu.Framework.Configuration
ShowLogOverlay,
AudioDevice,
AudioUseExperimentalWasapi,
VolumeUniversal,
VolumeEffect,
VolumeMusic,

View File

@@ -21,7 +21,6 @@ namespace osu.Framework
public static bool NoStructuredBuffers { get; }
public static string? DeferredRendererEventsOutputPath { get; }
public static bool UseSDL3 { get; }
public static bool UseWasapi { get; }
/// <summary>
/// Whether non-SSL requests should be allowed. Debug only. Defaults to disabled.
@@ -56,8 +55,6 @@ namespace osu.Framework
// Desktop has many issues, see https://github.com/ppy/osu-framework/issues/6540.
UseSDL3 = RuntimeInfo.IsMobile || (parseBool(Environment.GetEnvironmentVariable("OSU_SDL3")) ?? false);
UseWasapi = parseBool(Environment.GetEnvironmentVariable("OSU_AUDIO_WASAPI_EXPERIMENTAL")) ?? false;
}
private static bool? parseBool(string? value)

View File

@@ -196,6 +196,7 @@ namespace osu.Framework
// attach our bindables to the audio subsystem.
config.BindWith(FrameworkSetting.AudioDevice, Audio.AudioDevice);
config.BindWith(FrameworkSetting.AudioUseExperimentalWasapi, Audio.UseExperimentalWasapi);
config.BindWith(FrameworkSetting.VolumeUniversal, Audio.Volume);
config.BindWith(FrameworkSetting.VolumeEffect, Audio.VolumeSample);
config.BindWith(FrameworkSetting.VolumeMusic, Audio.VolumeTrack);

View File

@@ -130,7 +130,7 @@ namespace osu.Framework.Threading
/// </summary>
private readonly Bindable<int?> globalMixerHandle = new Bindable<int?>();
internal bool InitDevice(int deviceId)
internal bool InitDevice(int deviceId, bool useExperimentalWasapi)
{
Debug.Assert(ThreadSafety.IsAudioThread);
Trace.Assert(deviceId != -1); // The real device ID should always be used, as the -1 device has special cases which are hard to work with.
@@ -139,9 +139,7 @@ namespace osu.Framework.Threading
if (!Bass.Init(deviceId, Flags: (DeviceInitFlags)128)) // 128 == BASS_DEVICE_REINIT
return false;
// That this has not been mass-tested since https://github.com/ppy/osu-framework/pull/6651 and probably needs to be.
// Currently envvar gated for users to test at their own discretion.
if (FrameworkEnvironment.UseWasapi)
if (useExperimentalWasapi)
attemptWasapiInitialisation();
initialised_devices.Add(deviceId);
@@ -182,10 +180,10 @@ namespace osu.Framework.Threading
}
}
private void attemptWasapiInitialisation()
private bool attemptWasapiInitialisation()
{
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
return;
return false;
Logger.Log("Attempting local BassWasapi initialisation");
@@ -219,10 +217,10 @@ namespace osu.Framework.Threading
// To keep things in a sane state let's only keep one device initialised via wasapi.
freeWasapi();
initWasapi(wasapiDevice);
return initWasapi(wasapiDevice);
}
private void initWasapi(int wasapiDevice)
private bool initWasapi(int wasapiDevice)
{
// This is intentionally initialised inline and stored to a field.
// If we don't do this, it gets GC'd away.
@@ -246,13 +244,14 @@ namespace osu.Framework.Threading
Logger.Log($"Initialising BassWasapi for device {wasapiDevice}...{(initialised ? "success!" : "FAILED")}");
if (!initialised)
return;
return false;
BassWasapi.GetInfo(out var wasapiInfo);
globalMixerHandle.Value = BassMix.CreateMixerStream(wasapiInfo.Frequency, wasapiInfo.Channels, BassFlags.MixerNonStop | BassFlags.Decode | BassFlags.Float);
BassWasapi.Start();
BassWasapi.SetNotify(wasapiNotifyProcedure);
return true;
}
private void freeWasapi()