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.
This commit is contained in:
@@ -29,6 +29,8 @@ public sealed class AudioMixer : IDisposable
|
||||
private readonly Func<double>? _micGain;
|
||||
private readonly Func<double>? _loopbackGain;
|
||||
private readonly TimeSpan _mixInterval;
|
||||
private readonly Func<int>? _syncOffsetMs;
|
||||
private readonly AudioSyncDelay _syncDelay;
|
||||
private bool _started;
|
||||
|
||||
// Live path (TASK 9): per-source resampling → voice chain on the mic →
|
||||
@@ -45,6 +47,7 @@ public sealed class AudioMixer : IDisposable
|
||||
private float[]? _micChunk;
|
||||
private float[]? _loopbackChunk;
|
||||
private float[]? _mixBuffer;
|
||||
private float[]? _delayedMix;
|
||||
|
||||
public AudioMixer(
|
||||
IAudioSource mic,
|
||||
@@ -52,6 +55,7 @@ public sealed class AudioMixer : IDisposable
|
||||
Action<string>? log = null,
|
||||
Func<double>? micGain = null,
|
||||
Func<double>? loopbackGain = null,
|
||||
Func<int>? syncOffsetMs = null,
|
||||
TimeSpan? mixInterval = null)
|
||||
{
|
||||
_mic = mic;
|
||||
@@ -61,6 +65,8 @@ public sealed class AudioMixer : IDisposable
|
||||
_log = log;
|
||||
_micGain = micGain;
|
||||
_loopbackGain = loopbackGain;
|
||||
_syncOffsetMs = syncOffsetMs;
|
||||
_syncDelay = new AudioSyncDelay(OutputSampleRate);
|
||||
_mixInterval = mixInterval ?? DefaultMixInterval;
|
||||
|
||||
var seconds = _mixInterval.TotalSeconds;
|
||||
@@ -333,6 +339,13 @@ public sealed class AudioMixer : IDisposable
|
||||
mix[i * 2 + 1] = m + loopbackChunk[i * 2 + 1] * loopGain;
|
||||
}
|
||||
_masterLimiter.Process(mix);
|
||||
|
||||
_syncDelay.Configure(_syncOffsetMs?.Invoke() ?? 0);
|
||||
var delayed = _delayedMix!;
|
||||
if (delayed.Length < mix.Length)
|
||||
delayed = _delayedMix = new float[mix.Length];
|
||||
_syncDelay.Process(mix, delayed);
|
||||
Array.Copy(delayed, mix, mix.Length);
|
||||
return micRms;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user