维护代码质量

This commit is contained in:
LA
2026-02-15 18:05:53 +08:00
parent 5d52ef12e6
commit 362fcea81f
14 changed files with 134 additions and 109 deletions

View File

@@ -44,6 +44,7 @@ namespace osu.Framework.Tests.Audio
// Create a track
var track = audioManager.Tracks.Get("Resources.Tracks.sample-track.mp3");
if (track == null)
{
TestContext.WriteLine("Test audio file not available, but device switching worked");

View File

@@ -35,7 +35,7 @@ namespace osu.Framework.Tests.Audio
Assert.Ignore("No ASIO devices available for testing");
// Test switching to each ASIO device
foreach (var asioDevice in asioDevices)
foreach (string? asioDevice in asioDevices)
{
TestContext.WriteLine($"Testing switch to ASIO device: {asioDevice}");

View File

@@ -34,7 +34,7 @@ namespace osu.Framework.Tests.Audio
Assert.Ignore("No ASIO devices available for testing");
// Get current device before test
string originalDevice = audioManager.AudioDevice.Value;
// string originalDevice = audioManager.AudioDevice.Value;
// Try to switch to an ASIO device that might fail
// We'll use the first one, but the test environment might succeed
@@ -53,14 +53,7 @@ namespace osu.Framework.Tests.Audio
// In test environment, it might succeed, but in real world with failing ASIO,
// it should fallback to default (empty string) or original device
if (currentDevice != testDevice)
{
TestContext.WriteLine("ASIO device failed and fell back to another device - this is expected behavior");
}
else
{
TestContext.WriteLine("ASIO device was successfully selected");
}
TestContext.WriteLine(currentDevice != testDevice ? "ASIO device failed and fell back to another device - this is expected behavior" : "ASIO device was successfully selected");
// The key test is that the system doesn't crash and audio still works
// We can't easily test the actual fallback in test environment since ASIO works here

View File

@@ -34,6 +34,7 @@ namespace osu.Framework.Tests.Audio
{
Console.WriteLine("Available audio devices:");
var devices = audioManager.AudioDeviceNames.ToList();
for (int i = 0; i < devices.Count; i++)
{
Console.WriteLine($"{i}: {devices[i]}");
@@ -42,7 +43,7 @@ namespace osu.Framework.Tests.Audio
var asioDevices = devices.Where(d => d.Contains("(ASIO)")).ToList();
Console.WriteLine($"\nFound {asioDevices.Count} ASIO devices:");
foreach (var device in asioDevices)
foreach (string? device in asioDevices)
{
Console.WriteLine($" - {device}");
}
@@ -54,7 +55,7 @@ namespace osu.Framework.Tests.Audio
}
// Test switching to first ASIO device
var testDevice = asioDevices.First();
string? testDevice = asioDevices.First();
Console.WriteLine($"\nTesting ASIO device: {testDevice}");
audioManager.AudioDevice.Value = testDevice;

View File

@@ -73,6 +73,7 @@ namespace osu.Framework.Tests.Audio
try
{
int count = 0;
foreach (var device in AsioDeviceManager.AvailableDevices)
{
Console.WriteLine($"ASIO Device {device.Index}: {device.Name}");

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using ManagedBass;
using ManagedBass.Asio;
using osu.Framework.Logging;
@@ -57,13 +58,15 @@ namespace osu.Framework.Audio.Asio
/// 当ASIO设备初始化时由音频线程设置。
/// </summary>
private static int globalMixerHandle;
// 自动重新初始化监控
private static System.Threading.CancellationTokenSource? reinitMonitorCts;
private static CancellationTokenSource? reinitMonitorCts;
private static int? lastInitializedDeviceIndex;
private static double? lastRequestedSampleRate;
private static int? lastRequestedBufferSize;
// 用于防止重复的自动重新初始化同时进行
private static int reinitInProgress = 0;
private static int reinitInProgress;
private static Action? notifierHandler;
/// <summary>
@@ -116,6 +119,7 @@ namespace osu.Framework.Audio.Asio
/// </summary>
/// <param name="deviceIndex">设备索引。</param>
/// <param name="flags">初始化标志。</param>
/// <param name="allowRetries"></param>
/// <returns>如果初始化成功则为true否则为false。</returns>
private static bool tryInitializeDevice(int deviceIndex, AsioInitFlags flags, bool allowRetries = true)
{
@@ -284,7 +288,7 @@ namespace osu.Framework.Audio.Asio
if (tryInitializeDevice(deviceIndex, flags, allowRetries: false))
{
initialized = true;
// initialized = true;
break;
}
@@ -359,7 +363,7 @@ namespace osu.Framework.Audio.Asio
}
else
{
Logger.Log($"ASIO device did not report a usable rate; failing initialization", LoggingTarget.Runtime, LogLevel.Error);
Logger.Log("ASIO device did not report a usable rate; failing initialization", LoggingTarget.Runtime, LogLevel.Error);
FreeDevice();
return false;
}
@@ -375,25 +379,24 @@ namespace osu.Framework.Audio.Asio
lastInitializedDeviceIndex = deviceIndex;
lastRequestedSampleRate = sampleRateToTry ?? successfulRate;
lastRequestedBufferSize = bufferSize;
// 先尝试使用 CoreAudio 通知MMDevice进行事件驱动的变更检测
try
{
notifierHandler = () =>
{
// 确保不会并发执行多个重新初始化
if (System.Threading.Interlocked.CompareExchange(ref reinitInProgress, 1, 0) != 0)
if (Interlocked.CompareExchange(ref reinitInProgress, 1, 0) != 0)
return;
System.Threading.Tasks.Task.Run(() =>
Task.Run(() =>
{
try
{
Logger.Log("ASIO device notification received from system, triggering reinit.", LoggingTarget.Runtime, LogLevel.Important);
FreeDevice();
if (lastInitializedDeviceIndex.HasValue)
{
InitializeDevice(lastInitializedDeviceIndex.Value, lastRequestedSampleRate, lastRequestedBufferSize);
}
InitializeDevice(lastInitializedDeviceIndex.Value, lastRequestedSampleRate, lastRequestedBufferSize);
}
catch (Exception ex)
{
@@ -401,7 +404,7 @@ namespace osu.Framework.Audio.Asio
}
finally
{
System.Threading.Interlocked.Exchange(ref reinitInProgress, 0);
Interlocked.Exchange(ref reinitInProgress, 0);
}
});
};
@@ -730,6 +733,7 @@ namespace osu.Framework.Audio.Asio
AsioProcedure asioCallback = asioProcedure;
int outputs = Math.Max(0, info.Outputs);
if (outputs < 2)
{
Logger.Log($"Not enough ASIO outputs ({outputs}) to configure stereo", LoggingTarget.Runtime, LogLevel.Error);
@@ -817,7 +821,7 @@ namespace osu.Framework.Audio.Asio
{
foreach (ProcessModule mod in proc.Modules)
{
if (mod.ModuleName?.Equals("bassasio.dll", StringComparison.OrdinalIgnoreCase) == true)
if (mod.ModuleName.Equals("bassasio.dll", StringComparison.OrdinalIgnoreCase))
{
result.Add((proc.Id, proc.ProcessName));
break;
@@ -844,12 +848,13 @@ namespace osu.Framework.Audio.Asio
{
stopReinitMonitor();
reinitMonitorCts = new System.Threading.CancellationTokenSource();
reinitMonitorCts = new CancellationTokenSource();
var token = reinitMonitorCts.Token;
System.Threading.Tasks.Task.Run(() =>
Task.Run(() =>
{
AsioInfo lastInfo = default;
try
{
BassAsio.GetInfo(out lastInfo);
@@ -857,7 +862,9 @@ namespace osu.Framework.Audio.Asio
catch { }
double lastRate = 0;
try { lastRate = BassAsio.Rate; } catch { }
try { lastRate = BassAsio.Rate; }
catch { }
while (!token.IsCancellationRequested)
{
@@ -865,14 +872,19 @@ namespace osu.Framework.Audio.Asio
{
// 检查采样率或通道数变化
AsioInfo info;
if (BassAsio.GetInfo(out info))
{
double rate = 0;
try { rate = BassAsio.Rate; } catch { }
try { rate = BassAsio.Rate; }
catch { }
if (info.Inputs != lastInfo.Inputs || info.Outputs != lastInfo.Outputs || Math.Abs(rate - lastRate) > 0.5)
{
Logger.Log($"ASIO device parameters changed (inputs:{lastInfo.Inputs}->{info.Inputs}, outputs:{lastInfo.Outputs}->{info.Outputs}, rate:{lastRate}->{rate}). Triggering reinit.", LoggingTarget.Runtime, LogLevel.Important);
Logger.Log(
$"ASIO device parameters changed (inputs:{lastInfo.Inputs}->{info.Inputs}, outputs:{lastInfo.Outputs}->{info.Outputs}, rate:{lastRate}->{rate}). Triggering reinit.",
LoggingTarget.Runtime, LogLevel.Important);
// 执行重新初始化:释放并使用原参数重新初始化
try
@@ -900,7 +912,7 @@ namespace osu.Framework.Audio.Asio
// 忽略单次检测错误
}
System.Threading.Thread.Sleep(checkIntervalMs);
Thread.Sleep(checkIntervalMs);
}
}, token);
}

View File

@@ -1,3 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Runtime.InteropServices;
@@ -9,8 +12,8 @@ namespace osu.Framework.Audio.Asio
{
public static event Action? DeviceChanged;
private static IMMNotificationClientImpl? client;
private static IMMDeviceEnumerator? enumerator;
private static ImmNotificationClientImpl? client;
private static IMmDeviceEnumerator? enumerator;
public static void Start()
{
@@ -18,9 +21,9 @@ namespace osu.Framework.Audio.Asio
try
{
enumerator = (IMMDeviceEnumerator)new MMDeviceEnumerator();
client = new IMMNotificationClientImpl();
client.DeviceChanged += () => DeviceChanged?.Invoke();
enumerator = (IMmDeviceEnumerator)new MMDeviceEnumerator();
client = new ImmNotificationClientImpl();
try
{
// RegisterEndpointNotificationCallback returns an HRESULT; ignore non-zero gracefully
@@ -30,7 +33,6 @@ namespace osu.Framework.Audio.Asio
{
// swallow registry failures and stop notifier to allow fallback polling
Stop();
return;
}
}
catch
@@ -42,14 +44,14 @@ namespace osu.Framework.Audio.Asio
public static void Stop()
{
try
try
{
if (enumerator != null && client != null)
{
if (enumerator != null && client != null)
{
_ = enumerator.UnregisterEndpointNotificationCallback(client);
}
_ = enumerator.UnregisterEndpointNotificationCallback(client);
}
catch { }
}
catch { }
finally
{
client = null;
@@ -57,10 +59,8 @@ namespace osu.Framework.Audio.Asio
}
}
private class IMMNotificationClientImpl : IMMNotificationClient
private class ImmNotificationClientImpl : IMmNotificationClient
{
public event Action? DeviceChanged;
public void OnDeviceStateChanged(string pwstrDeviceId, int dwNewState)
{
DeviceChanged?.Invoke();
@@ -81,7 +81,7 @@ namespace osu.Framework.Audio.Asio
DeviceChanged?.Invoke();
}
public void OnPropertyValueChanged(string pwstrDeviceId, PROPERTYKEY key)
public void OnPropertyValueChanged(string pwstrDeviceId, PropertyKey key)
{
DeviceChanged?.Invoke();
}
@@ -90,31 +90,38 @@ namespace osu.Framework.Audio.Asio
#region COM interop
[ComImport, Guid("BCDE0395-E52F-467C-8E3D-C4579291692E")]
private class MMDeviceEnumerator { }
private class MMDeviceEnumerator
{
}
private enum EDataFlow { eRender = 0, eCapture = 1, eAll = 2 }
private enum ERole { eConsole = 0, eMultimedia = 1, eCommunications = 2 }
private enum EDataFlow { ERender = 0, ECapture = 1, EAll = 2 }
private enum ERole { EConsole = 0, EMultimedia = 1, ECommunications = 2 }
[StructLayout(LayoutKind.Sequential)]
private struct PROPERTYKEY { public Guid fmtid; public int pid; }
private struct PropertyKey
{
public Guid fmtID;
public int pid;
}
[Guid("A95664D2-9614-4F35-A746-DE8DB63617E6"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IMMDeviceEnumerator
private interface IMmDeviceEnumerator
{
int NotImpl1();
int NotImpl2();
int RegisterEndpointNotificationCallback(IMMNotificationClient client);
int UnregisterEndpointNotificationCallback(IMMNotificationClient client);
int RegisterEndpointNotificationCallback(IMmNotificationClient client);
int UnregisterEndpointNotificationCallback(IMmNotificationClient client);
}
[Guid("7991EEC9-7E89-4D85-8390-6C703CEC60C0"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
private interface IMMNotificationClient
private interface IMmNotificationClient
{
void OnDeviceStateChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, int dwNewState);
void OnDeviceAdded([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId);
void OnDeviceRemoved([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId);
void OnDefaultDeviceChanged(EDataFlow flow, ERole role, [MarshalAs(UnmanagedType.LPWStr)] string pwstrDefaultDeviceId);
void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PROPERTYKEY key);
void OnPropertyValueChanged([MarshalAs(UnmanagedType.LPWStr)] string pwstrDeviceId, PropertyKey key);
}
#endregion

View File

@@ -73,12 +73,12 @@ namespace osu.Framework.Audio
/// <summary>
/// 采样率用于ASIO设备的初始化和运行时更改。
/// </summary>
public readonly Bindable<int> SAMPLE_RATE = new Bindable<int>(48000);
public readonly Bindable<int> SampleRate = new Bindable<int>(48000);
/// <summary>
/// ASIO缓冲区大小默认为128用于ASIO设备的初始化。
/// </summary>
public readonly Bindable<int> ASIO_BUFFER_SIZE = new Bindable<int>(128);
public readonly Bindable<int> AsioBufferSize = new Bindable<int>(128);
/// <summary>
/// The names of all available audio devices.
@@ -237,7 +237,7 @@ namespace osu.Framework.Audio
/// <param name="sampleRate">The preferred sample rate in Hz.</param>
public void SetPreferredAsioSampleRate(int sampleRate)
{
SAMPLE_RATE.Value = sampleRate;
SampleRate.Value = sampleRate;
}
/// <summary>
@@ -246,7 +246,7 @@ namespace osu.Framework.Audio
/// <param name="bufferSize">The preferred buffer size for ASIO device.</param>
public void SetAsioBufferSize(int bufferSize)
{
ASIO_BUFFER_SIZE.Value = bufferSize;
AsioBufferSize.Value = bufferSize;
}
/// <summary>
@@ -328,7 +328,7 @@ namespace osu.Framework.Audio
GlobalMixerHandle.ValueChanged += handle => usingGlobalMixer.Value = handle.NewValue.HasValue;
// Listen for unified sample rate changes and reinitialize device if supported
SAMPLE_RATE.ValueChanged += e =>
SampleRate.ValueChanged += e =>
{
if (syncingSelection)
return;
@@ -340,7 +340,7 @@ namespace osu.Framework.Audio
// Only reinitialize if we're currently using an ASIO device (only ASIO supports runtime sample rate changes)
if (hasTypeSuffix(AudioDevice.Value) && tryParseSuffixed(AudioDevice.Value, type_asio, out string _))
{
Logger.Log($"Sample rate changed to {SAMPLE_RATE.Value}Hz, reinitializing ASIO device", name: "audio", level: LogLevel.Important);
Logger.Log($"Sample rate changed to {SampleRate.Value}Hz, reinitializing ASIO device", name: "audio", level: LogLevel.Important);
Logger.Log($"Current audio device before reinitialization: {AudioDevice.Value}", name: "audio", level: LogLevel.Debug);
scheduler.AddOnce(() =>
@@ -358,7 +358,7 @@ namespace osu.Framework.Audio
}
else
{
Logger.Log($"Sample rate changed to {SAMPLE_RATE.Value}Hz, but current device ({AudioDevice.Value}) does not support runtime sample rate changes", name: "audio",
Logger.Log($"Sample rate changed to {SampleRate.Value}Hz, but current device ({AudioDevice.Value}) does not support runtime sample rate changes", name: "audio",
level: LogLevel.Debug);
syncingSelection = false;
isChangingAsioSettings = false;
@@ -372,7 +372,7 @@ namespace osu.Framework.Audio
};
// Listen for ASIO buffer size changes and reinitialize device if supported
ASIO_BUFFER_SIZE.ValueChanged += e =>
AsioBufferSize.ValueChanged += e =>
{
if (syncingSelection)
return;
@@ -401,7 +401,7 @@ namespace osu.Framework.Audio
// Only reinitialize if we're currently using an ASIO device (only ASIO supports runtime buffer size changes)
if (hasTypeSuffix(AudioDevice.Value) && tryParseSuffixed(AudioDevice.Value, type_asio, out string _))
{
Logger.Log($"ASIO buffer size changed to {ASIO_BUFFER_SIZE.Value}, reinitializing ASIO device", name: "audio", level: LogLevel.Important);
Logger.Log($"ASIO buffer size changed to {AsioBufferSize.Value}, reinitializing ASIO device", name: "audio", level: LogLevel.Important);
Logger.Log($"Current audio device before reinitialization: {AudioDevice.Value}", name: "audio", level: LogLevel.Debug);
scheduler.AddOnce(() =>
@@ -419,7 +419,7 @@ namespace osu.Framework.Audio
}
else
{
Logger.Log($"ASIO buffer size changed to {ASIO_BUFFER_SIZE.Value}, but current device ({AudioDevice.Value}) does not support runtime buffer size changes", name: "audio", level: LogLevel.Debug);
Logger.Log($"ASIO buffer size changed to {AsioBufferSize.Value}, but current device ({AudioDevice.Value}) does not support runtime buffer size changes", name: "audio", level: LogLevel.Debug);
syncingSelection = false;
isChangingAsioSettings = false;
}
@@ -787,7 +787,7 @@ namespace osu.Framework.Audio
try
{
innerSuccess = thread.InitDevice(device, outputMode, SAMPLE_RATE.Value);
innerSuccess = thread.InitDevice(device, outputMode, SampleRate.Value);
}
catch (Exception e)
{

View File

@@ -2,25 +2,25 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.IO;
using System.Linq;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using osu.Framework.Logging;
using osu.Framework.Threading;
namespace osu.Framework.Audio.EzLatency
{
#nullable disable
// --- Structures and Record -------------------------------------------------
#nullable disable
public struct EzLatencyInputData
{
public double InputTime;
public object KeyValue;
public double JudgeTime;
public double PlaybackTime;
// Consider input+playback or input+judge as valid for best-effort measurements.
public bool IsValid => InputTime > 0 && (PlaybackTime > 0 || JudgeTime > 0);
}
@@ -67,7 +67,7 @@ namespace osu.Framework.Audio.EzLatency
public class EzLatencyAnalyzer
{
private readonly Stopwatch stopwatch;
public bool Enabled { get; set; } = false;
public bool Enabled { get; set; }
public event Action<EzLatencyRecord> OnNewRecord;
private EzLatencyInputData currentInputData;
@@ -83,6 +83,7 @@ namespace osu.Framework.Audio.EzLatency
public void RecordInputData(double inputTime, object keyValue = null)
{
if (!Enabled) return;
if (currentInputData.InputTime > 0)
{
currentInputData = default;
@@ -97,6 +98,7 @@ namespace osu.Framework.Audio.EzLatency
public void RecordJudgeData(double judgeTime)
{
if (!Enabled) return;
currentInputData.JudgeTime = judgeTime;
checkTimeout();
}
@@ -104,6 +106,7 @@ namespace osu.Framework.Audio.EzLatency
public void RecordPlaybackData(double playbackTime)
{
if (!Enabled) return;
currentInputData.PlaybackTime = playbackTime;
tryGenerateCompleteRecord();
}
@@ -111,6 +114,7 @@ namespace osu.Framework.Audio.EzLatency
public void RecordHardwareData(double driverTime, double outputHardwareTime, double inputHardwareTime, double latencyDifference)
{
if (!Enabled) return;
currentHardwareData = new EzLatencyHardwareData
{
DriverTime = driverTime,
@@ -142,9 +146,11 @@ namespace osu.Framework.Audio.EzLatency
InputHardwareTime = currentHardwareData.InputHardwareTime,
LatencyDifference = currentHardwareData.LatencyDifference,
// MeasuredMs: prefer Playback - Input when available, otherwise use Judge - Input as a best-effort.
MeasuredMs = (currentInputData.PlaybackTime > 0)
MeasuredMs = currentInputData.PlaybackTime > 0
? currentInputData.PlaybackTime - currentInputData.InputTime
: (currentInputData.JudgeTime > 0 ? currentInputData.JudgeTime - currentInputData.InputTime : 0),
: currentInputData.JudgeTime > 0
? currentInputData.JudgeTime - currentInputData.InputTime
: 0,
Note = currentHardwareData.IsValid ? "complete-latency-measurement" : "best-effort-no-hw",
InputData = currentInputData,
HardwareData = currentHardwareData
@@ -152,17 +158,17 @@ namespace osu.Framework.Audio.EzLatency
try
{
OnNewRecord?.Invoke(record);
EzLatencyService.Instance.PushRecord(record);
if (currentHardwareData.IsValid)
Logger.Log($"EzLatency 完整记录已生成: Input→Playback={record.PlaybackTime - record.InputTime:F2}ms", LoggingTarget.Runtime, LogLevel.Debug);
else
Logger.Log($"EzLatency 最佳尝试记录(无硬件时间戳): Input→Playback={record.PlaybackTime - record.InputTime:F2}ms", LoggingTarget.Runtime, LogLevel.Debug);
OnNewRecord?.Invoke(record);
EzLatencyService.Instance.PushRecord(record);
Logger.Log(
currentHardwareData.IsValid
? $"EzLatency 完整记录已生成: Input→Playback={record.PlaybackTime - record.InputTime:F2}ms"
: $"EzLatency 最佳尝试记录(无硬件时间戳): Input→Playback={record.PlaybackTime - record.InputTime:F2}ms", LoggingTarget.Runtime, LogLevel.Debug);
}
catch (Exception ex)
{
Logger.Log($"EzLatencyAnalyzer: tryGenerateCompleteRecord failed: {ex.Message}", LoggingTarget.Runtime, LogLevel.Error);
}
catch (Exception ex)
{
Logger.Log($"EzLatencyAnalyzer: tryGenerateCompleteRecord failed: {ex.Message}", LoggingTarget.Runtime, LogLevel.Error);
}
ClearCurrentData();
}
@@ -174,6 +180,7 @@ namespace osu.Framework.Audio.EzLatency
if (recordStartTime > 0)
{
double elapsed = stopwatch.Elapsed.TotalMilliseconds - recordStartTime;
if (elapsed > timeout_ms)
{
Logger.Log($"EzLatency 数据收集超时 ({elapsed:F0}ms),清除旧数据", LoggingTarget.Runtime, LogLevel.Debug);
@@ -225,14 +232,14 @@ namespace osu.Framework.Audio.EzLatency
public class EzLoggerAdapter : IEzLatencyLogger
{
private readonly Scheduler scheduler;
private StreamWriter fileWriter;
private readonly StreamWriter fileWriter;
public event Action<EzLatencyRecord> OnRecord;
public EzLoggerAdapter(Scheduler scheduler = null, string filePath = null)
{
this.scheduler = scheduler;
this.scheduler = scheduler as Scheduler;
this.scheduler = scheduler;
{
try
{
@@ -343,7 +350,9 @@ namespace osu.Framework.Audio.EzLatency
public void RecordInputData(double inputTime, object keyValue = null) => analyzer.RecordInputData(inputTime, keyValue);
public void RecordJudgeData(double judgeTime) => analyzer.RecordJudgeData(judgeTime);
public void RecordPlaybackData(double playbackTime) => analyzer.RecordPlaybackData(playbackTime);
public void RecordHardwareData(double driverTime, double outputHardwareTime, double inputHardwareTime, double latencyDifference) => analyzer.RecordHardwareData(driverTime, outputHardwareTime, inputHardwareTime, latencyDifference);
public void RecordHardwareData(double driverTime, double outputHardwareTime, double inputHardwareTime, double latencyDifference) =>
analyzer.RecordHardwareData(driverTime, outputHardwareTime, inputHardwareTime, latencyDifference);
}
// --- Statistics & Collector ----------------------------------------------
@@ -394,22 +403,22 @@ namespace osu.Framework.Audio.EzLatency
// Filter out invalid or incomplete differences (<= 0) to avoid extreme/garbage values.
var inputToJudge = records
.Where(r => r.InputTime > 0 && r.JudgeTime > 0)
.Select(r => r.JudgeTime - r.InputTime)
.Where(d => Math.Abs(d) <= 1000) // sanity cap: ignore absurdly large diffs (>1000ms)
.ToList();
.Where(r => r.InputTime > 0 && r.JudgeTime > 0)
.Select(r => r.JudgeTime - r.InputTime)
.Where(d => Math.Abs(d) <= 1000) // sanity cap: ignore absurdly large diffs (>1000ms)
.ToList();
var inputToPlayback = records
.Where(r => r.InputTime > 0 && r.PlaybackTime > 0)
.Select(r => r.PlaybackTime - r.InputTime)
.Where(d => Math.Abs(d) <= 1000)
.ToList();
.Where(r => r.InputTime > 0 && r.PlaybackTime > 0)
.Select(r => r.PlaybackTime - r.InputTime)
.Where(d => Math.Abs(d) <= 1000)
.ToList();
var playbackToJudge = records
.Where(r => r.PlaybackTime > 0 && r.JudgeTime > 0)
.Select(r => r.JudgeTime - r.PlaybackTime)
.Where(d => Math.Abs(d) <= 1000)
.ToList();
.Where(r => r.PlaybackTime > 0 && r.JudgeTime > 0)
.Select(r => r.JudgeTime - r.PlaybackTime)
.Where(d => Math.Abs(d) <= 1000)
.ToList();
var hardwareLatency = records.Select(r => r.OutputHardwareTime).Where(h => h > 0).ToList();

View File

@@ -25,10 +25,11 @@ namespace osu.Framework.Audio.Sample
ObjectDisposedException.ThrowIf(IsDisposed, this);
Played = true;
try
{
// Best-effort: notify global latency manager that a sample playback was requested.
osu.Framework.Audio.EzLatency.EzLatencyManager.GLOBAL.RecordPlaybackEvent();
EzLatency.EzLatencyManager.GLOBAL.RecordPlaybackEvent();
}
catch
{

View File

@@ -132,14 +132,14 @@ namespace osu.Framework.Input
}
}
#if DEBUG
#if DEBUG
if (handledBy != null)
{
Logger.Log(SuppressLoggingEventInformation(handledBy)
? $"{e.GetType().Name} handled by {handledBy}."
: $"{e} handled by {handledBy}.", LoggingTarget.Runtime, LogLevel.Debug);
}
#endif
#endif
return handledBy;
}

View File

@@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
// using osu.Framework.Extensions.TypeExtensions;
using osu.Framework.Input.States;
using osuTK.Input;

View File

@@ -10,6 +10,7 @@ using JetBrains.Annotations;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.Input.States;
// using osu.Framework.Logging;
namespace osu.Framework.Input

View File

@@ -311,7 +311,7 @@ namespace osu.Framework.Threading
}
else
{
Logger.Log($"无法找到ASIO设备: {deviceName}", name: "audio", level: LogLevel.Error);
Logger.Log($"无法找到ASIO设备: {deviceName}, Mode: {mode}", name: "audio", level: LogLevel.Error);
return false;
}
}
@@ -687,8 +687,8 @@ namespace osu.Framework.Threading
// 使用来自AudioManager的统一采样率和缓冲区大小
if (Manager != null)
{
preferredSampleRate = Manager.SAMPLE_RATE.Value;
int bufferSize = Manager.ASIO_BUFFER_SIZE.Value;
preferredSampleRate = Manager.SampleRate.Value == 0 ? preferredSampleRate : Manager.SampleRate.Value;
int bufferSize = Manager.AsioBufferSize.Value;
// 验证当前设备是否已在运行,如果是则先停止
if (AsioDeviceManager.IsDeviceRunning())
@@ -718,7 +718,7 @@ namespace osu.Framework.Threading
}
int outputChannels = Math.Max(1, deviceInfo.Value.Outputs);
int inputChannels = Math.Max(0, deviceInfo.Value.Inputs);
// int inputChannels = Math.Max(0, deviceInfo.Value.Inputs);
// 验证设备信息
if (outputChannels < 2)
@@ -898,8 +898,6 @@ namespace osu.Framework.Threading
}
}
#endregion
/// <summary>
@@ -918,7 +916,7 @@ namespace osu.Framework.Threading
}
// 检查是否为ASIO设备
string suffix = $" ({type_asio})";
const string suffix = $" ({type_asio})";
if (selection.EndsWith(suffix, StringComparison.Ordinal))
{