feat: signed A/V sync offset (−500..+500), negative advances by eating live stream head (OBS eat-head semantics)
Positive offsets still delay the whole mix via the delay line (lip-sync fix); negative offsets now ARM once at StartLive and drop |N| ms off the pipe's write head so audio events land earlier when audio runs BEHIND video. Slider relabeled AUDIO SYNC, Min −500, locked while live/recording (IsEditMode). LayoutStore and VM clamp to −500..500. OBS reference for eat-the-head negative sync: https://obsproject.com/kb/obs-studio/buffering-time (negative sync values pull audio earlier by discarding buffered player audio). Test: StartLive_NegativeOffset_AdvancesAudio_ByDroppingTheStreamHead (6x0.9 head must be eaten before 0.2 bed reaches the wire).
This commit is contained in:
@@ -626,11 +626,15 @@ devices, no timers).
|
||||
pipe lifecycle.
|
||||
- **Audio sync offset (TASK 22):** after the limiter, `FillAndMix` routes the whole interleaved stereo
|
||||
mix through a **`AudioSyncDelay`** (`Services/Audio/AudioSyncDelay.cs`) — a pure delay line whose
|
||||
offset comes from a `Func<int> syncOffsetMs` seam (the VM's global `AudioSyncOffsetMs`, 0..500 ms,
|
||||
persisted as `Audio.SyncOffsetMs`). This is OBS's documented fix for audio running ahead of the video
|
||||
(delay it a few ms); positive-only, since advancing audio would need a video-side delay. A slider on
|
||||
the mic bar + a status dot (`IntToSyncBrushConverter`) surface it. Changing the delay flushes the line
|
||||
(a live change clicks rather than smears).
|
||||
offset comes from a `Func<int> syncOffsetMs` seam (the VM's global `AudioSyncOffsetMs`, **−500..+500
|
||||
ms**, persisted as `Audio.SyncOffsetMs`). This is OBS's documented fix for lip-sync: **positive**
|
||||
delays audio when it runs ahead of video; **negative** advances audio when it runs behind by eating
|
||||
the first `|N|` ms of the live stream head (see `AudioMixer.StartLive`; the delay line stays
|
||||
clamped 0..500 internally — negative bypasses it entirely). The advance is armed once at `StartLive`
|
||||
(eating the head mid-stream is impossible); positive is live-reactive (re-read per tick). A slider
|
||||
on the mic bar (`"AUDIO SYNC"`, locked while live/recording via `IsEditMode`) + a status dot
|
||||
(`IntToSyncBrushConverter`) surface it. Changing the delay flushes the line (a live change clicks
|
||||
rather than smears).
|
||||
- **TRAX — free background music (TASK 8):** `MusicPlayer` = NAudio `MediaFoundationReader`
|
||||
(mp3/wav/m4a) → `VolumeWaveProvider16` at the hardcoded **0.20** bed (no slider) → `WaveOutEvent` on
|
||||
the default device, **looping on any clean natural end** (`PlaybackStopped` with `e.Exception == null`
|
||||
|
||||
Reference in New Issue
Block a user