[AudioOut2] Block a blocking push only until a queue slot frees
A blocking sceAudioOut2ContextPush waited only when the host stream hit its 128 KiB cap, so a title rendering faster than real time kept about 680 ms queued and every sound played that far behind (GTA V: queue=2 context, queued_ms avg=678). The push now waits until the device holds the cushion plus QueueDepth-1 grains, the level GetQueueLevel already reports as full; the host cap stays as the limit for non-blocking bursts. GTA V now holds about 41 ms with no underruns.
This commit is contained in:
parent
767bca9e26
commit
a7cc266702
|
|
@ -139,6 +139,9 @@ public static class AudioOut2Exports
|
|||
return ahead <= 0 ? 0 : (uint)Math.Min((ahead + GrainTicks - 1) / GrainTicks, QueueDepth);
|
||||
}
|
||||
|
||||
public double DeviceSlotFreeMilliseconds =>
|
||||
DeviceSlotFreeThreshold(GrainSamples, Frequency, QueueDepth);
|
||||
|
||||
// Grains the device holds beyond the cushion, at most QueueDepth.
|
||||
public uint DeviceQueuedGrains(int queuedMilliseconds)
|
||||
{
|
||||
|
|
@ -430,6 +433,14 @@ public static class AudioOut2Exports
|
|||
{
|
||||
context.DevicePaced = true;
|
||||
}
|
||||
|
||||
if (submitted || advancedGrain == 1)
|
||||
{
|
||||
if (blocking != 0)
|
||||
{
|
||||
WaitForDeviceSlot(context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Push is the blocking point of a grain: it waits for the grain an Advance claimed, or
|
||||
|
|
@ -1096,6 +1107,28 @@ public static class AudioOut2Exports
|
|||
}
|
||||
}
|
||||
|
||||
internal static double DeviceSlotFreeThreshold(uint grainSamples, uint frequency, uint queueDepth) =>
|
||||
DeviceCushionMilliseconds + (grainSamples * 1000.0 / frequency * Math.Max((long)queueDepth - 1, 0));
|
||||
|
||||
private static void WaitForDeviceSlot(ContextState context)
|
||||
{
|
||||
var backend = ResolveContextBackend(context, out _);
|
||||
if (backend is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var limit = context.DeviceSlotFreeMilliseconds;
|
||||
var deadline = Stopwatch.GetTimestamp() + (Stopwatch.Frequency / 4);
|
||||
int queued;
|
||||
while ((queued = backend.QueuedMilliseconds) > limit &&
|
||||
Stopwatch.GetTimestamp() < deadline &&
|
||||
!HostSessionControl.IsShutdownRequested)
|
||||
{
|
||||
Thread.Sleep((int)Math.Clamp(Math.Ceiling(queued - limit), 1, 50));
|
||||
}
|
||||
}
|
||||
|
||||
private static uint QueuedGrains(ContextState context)
|
||||
{
|
||||
if (context.DevicePaced)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
// Copyright (C) 2026 SharpEmu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
using SharpEmu.Libs.Audio;
|
||||
using Xunit;
|
||||
|
||||
namespace SharpEmu.Libs.Tests.Audio;
|
||||
|
||||
public sealed class AudioOut2PacingTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData(256u, 48000u, 1u, 40.0)]
|
||||
[InlineData(256u, 48000u, 2u, 45.333)]
|
||||
[InlineData(256u, 48000u, 4u, 56.0)]
|
||||
[InlineData(1024u, 48000u, 4u, 104.0)]
|
||||
public void ABlockingPushWaitsUntilTheDeviceHoldsTheCushionAndOneSlotLessThanTheQueue(
|
||||
uint grainSamples, uint frequency, uint queueDepth, double expected)
|
||||
{
|
||||
Assert.Equal(expected, AudioOut2Exports.DeviceSlotFreeThreshold(grainSamples, frequency, queueDepth), 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TheThresholdStaysFarBelowTheHostQueueCap()
|
||||
{
|
||||
const double hostCapMilliseconds = 128 * 1024 * 1000.0 / (48000 * 4);
|
||||
Assert.True(AudioOut2Exports.DeviceSlotFreeThreshold(256, 48000, 2) < hostCapMilliseconds / 10);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue