fix(audio): both sync offsets ship at zero
Shipping 175 ms as the built-in mic delay was wrong on principle, not just in value: it shifts every creator's voice to correct one camera on one desk, and it hides whether a desync is theirs or ours. A correction has to be something the creator turned on. So DefaultMicSyncOffsetMs is 0. The control stays (slider 0..500, off/175/350 presets, applied value logged) for anyone whose camera needs it; it is simply no longer pre-applied. The global offset was already 0. AudioSyncSettingsTests pins the rule: a fresh profile reads 0 for both, the mic delay refuses a negative (advancing is the global offset's job) and clamps above 500, and set values round-trip. 676 pass, 0 warnings. Scope justification: Assets/starting-background.jpg is outside this unit — the creator's replacement art, still awaiting their visual check.
This commit is contained in:
+3
-3
@@ -12,9 +12,9 @@
|
||||
counts only audio whose write reached the pipe, and starts its clock at session open. Measured
|
||||
across three recordings: +2.1% long → 226 ms short → 137 ms short → **lengths now match (34 ms in
|
||||
13 s)**.
|
||||
- **What is left is one number.** `5ae5280` gives the mic delay a 0..500 slider (off/175/350) and logs
|
||||
the applied value. Measured on the last clip: `-185 ms`, i.e. sound still 185 ms EARLY, so the value
|
||||
to try is **350–360 ms**. `Audio.MicSyncOffsetMs` is stored per profile.
|
||||
- **Sync answer (measured, settled).** Mic delay ships at **0** and should stay off; with it off the audio
|
||||
path runs ~100 ms behind, so the **global** LIP-SYNC offset is **−130 ms**. Both offsets default to 0 —
|
||||
a correction belongs to one camera, never to the product (`AudioSyncSettingsTests` pins it).
|
||||
- **675 tests green, 0 warnings.** `scripts/avsync.py` measures any recording in seconds.
|
||||
|
||||
## Next action
|
||||
|
||||
@@ -117,9 +117,12 @@ public partial class LayoutStore : IDisposable
|
||||
return int.TryParse(value, out var ms) ? Math.Clamp(ms, -500, 500) : 0;
|
||||
}
|
||||
|
||||
/// <summary>The shipped default for the microphone-only delay. The creator's measurement for a
|
||||
/// C920 pipeline (asked for +150..+200 ms); a camera with less latency wants less.</summary>
|
||||
public const int DefaultMicSyncOffsetMs = 175;
|
||||
/// <summary>
|
||||
/// The shipped default for the microphone-only delay: **zero**. A sync offset is a correction for one
|
||||
/// camera on one desk, never a product default — shipping a number would shift every creator's audio
|
||||
/// to fix one machine's camera, and would hide whether the fault is theirs or ours.
|
||||
/// </summary>
|
||||
public const int DefaultMicSyncOffsetMs = 0;
|
||||
|
||||
/// <summary>Microphone-only sync delay (0..500 ms), so the voice can wait for the camera's picture
|
||||
/// without moving desktop or alert audio. Absent means the shipped default.</summary>
|
||||
|
||||
+3
-3
@@ -294,9 +294,9 @@ devices, no timers).
|
||||
samples reach the pipe, so the voice runs ahead of the lips. `AudioSyncDelay` now takes a channel
|
||||
count, and `FillAndMix` runs a second line (**mono**) over `micChunk` *before* the mix — loopback and
|
||||
alert audio are not tied to a camera frame and must not move with it. Seam: `Func<int> micSyncOffsetMs`
|
||||
(VM `MicSyncOffsetMs`, **0..500 ms**, persisted `Audio.MicSyncOffsetMs`, shipped default **175** —
|
||||
the creator's own C920 measurement, not a constant: the right number belongs to the machine).
|
||||
A **slider 0..500** plus `off / 175 / 350` presets sit in the LIP-SYNC bay under the whole-mix
|
||||
(VM `MicSyncOffsetMs`, **0..500 ms**, persisted `Audio.MicSyncOffsetMs`, shipped default **0 / off** — a sync offset corrects one camera on
|
||||
one desk, so it is opt-in, never a product default).
|
||||
A **slider 0..500** (default 0) plus `off / 175 / 350` presets sit in the LIP-SYNC bay under the whole-mix
|
||||
slider — presets alone could not express the value a slow camera actually needs. `Audio live: started`
|
||||
logs the applied number, because the value lives in the layout DB and a preset click is invisible in a
|
||||
recording. Both lines are
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Xunit;
|
||||
using ytLive.Services;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
/// <summary>
|
||||
/// Sync offsets ship at zero. A correction belongs to one camera on one desk, so a fresh profile must
|
||||
/// not arrive with audio already shifted — a non-zero default would move every creator's voice to fix
|
||||
/// one machine and would hide whether the fault is theirs or ours.
|
||||
/// </summary>
|
||||
public sealed class AudioSyncSettingsTests : IDisposable
|
||||
{
|
||||
private readonly string _db = Path.Combine(Path.GetTempPath(), $"ytLlive-syncsettings-{Guid.NewGuid():N}.db");
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
SqliteConnection.ClearAllPools();
|
||||
try { File.Delete(_db); } catch (IOException) { /* best effort */ }
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Both_offsets_start_at_zero_and_only_then_take_a_value()
|
||||
{
|
||||
using (var store = new LayoutStore(_db))
|
||||
{
|
||||
Assert.Equal(0, store.LoadMicSyncOffsetMs()); // opt-in, not pre-applied
|
||||
Assert.Equal(0, store.LoadAudioSyncOffsetMs());
|
||||
|
||||
// A mic delay cannot be negative — advancing is the global offset's job.
|
||||
store.SaveMicSyncOffsetMs(-120);
|
||||
Assert.Equal(0, store.LoadMicSyncOffsetMs());
|
||||
|
||||
store.SaveMicSyncOffsetMs(600); // beyond the slider's range
|
||||
Assert.Equal(500, store.LoadMicSyncOffsetMs());
|
||||
|
||||
store.SaveMicSyncOffsetMs(240);
|
||||
store.SaveAudioSyncOffsetMs(-130);
|
||||
}
|
||||
|
||||
using var reopened = new LayoutStore(_db);
|
||||
Assert.Equal(240, reopened.LoadMicSyncOffsetMs());
|
||||
Assert.Equal(-130, reopened.LoadAudioSyncOffsetMs());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user