Files
LlamaCasty/ytLive.Tests/AudioSyncDelayTests.cs
T
gramps e8ff4df05f TASK 22: global audio sync offset (positive delay at the mixer out)
Adds AudioSyncOffsetMs (0..500ms, default 0) that delays the whole
interleaved-stereo mix so audio lands on the video when it runs ahead —
OBS's documented lip-sync fix. Positive-only: advancing audio needs a
video-side delay and is out of the audio layer's scope.

- Services/Audio/AudioSyncDelay.cs: pure delay line, flushed on Configure
- AudioMixer: Func<int> syncOffsetMs seam + delay applied post-limiter
- LayoutStore.Settings + MainViewModel.Audio/VM: load/save + binding
- PreviewPane mic bar: SYNC slider + status dot (IntToSyncBrushConverter)
- AudioSyncDelayTests: identity, negative/beyond-500 clamps, 10ms→960 samples

Reference (external scan): https://obs-versions.com/blog/how-to-fix-audio-delay-on-obs
(audio ahead => positive delay). Verified: build 0 warnings; 3/3 delay tests pass.
2026-08-31 18:30:03 -07:00

78 lines
2.2 KiB
C#

using Xunit;
using ytLive.Services.Audio;
namespace ytLive.Tests;
/// <summary>
/// TASK 22: the global audio-sync delay line. It is a pure fixed positive
/// delay on the interleaved stereo mix — the OBS fix for audio running ahead
/// of video. Positive-only: advancing audio needs a video-side delay and is
/// out of the audio layer's scope.
/// </summary>
public class AudioSyncDelayTests
{
[Fact]
public void ZeroDelay_PassesThroughUnchanged()
{
var delay = new AudioSyncDelay(48000);
delay.Configure(0);
var input = new float[16];
for (var i = 0; i < input.Length; i++)
input[i] = i - 8;
var output = new float[16];
delay.Process(input, output);
Assert.Equal(input, output);
}
[Fact]
public void ConfigureClampsNegativeToZero_AndBeyond500msTo500ms()
{
var delay = new AudioSyncDelay(48000);
delay.Configure(-50);
Assert.Equal(0, MarkerShift(delay, 1000));
// 500 ms at 48 kHz = 24000 frames = 48000 interleaved samples.
delay.Configure(6000);
Assert.Equal(48000, MarkerShift(delay, 48000));
}
[Fact]
public void TenMsDelay_EmitsTheSignal960SamplesLater()
{
// 10 ms at 48 kHz = 480 frames; interleaved stereo = 960 samples.
var delay = new AudioSyncDelay(48000);
Assert.Equal(960, MarkerShift(delay, 480 * 2, configureMs: 10));
}
private static int MarkerShift(AudioSyncDelay delay, int chunkLen, int configureMs = -1)
{
if (configureMs >= 0)
delay.Configure(configureMs);
var chunk = new float[chunkLen];
var output = new float[chunkLen];
chunk[0] = 1f; // sole non-zero: left of frame 0
var foundAt = -1;
var total = 0;
for (var c = 0; c < 4 && foundAt < 0; c++)
{
delay.Process(chunk, output);
for (var i = 0; i < chunkLen; i++)
{
if (output[i] != 0f)
{
foundAt = total + i;
break;
}
}
total += chunkLen;
}
Assert.True(foundAt >= 0, "marker never appeared in the delayed output");
return foundAt;
}
}