fix(rec): recordings played ~1.4x fast — CFR emission, drop -re
Root cause (measured, not guessed): the take-3 'rebase on overrun' reset
nextTick to wall-now, erasing every missed slot. Pump delivered 215-219/300
per 5s (~43fps) but rawvideo carries no timestamps — ffmpeg muxes by frame
count at -framerate 60, so every recording played fast with honest-looking
stats. A second pacer, ffmpeg -re, throttled the demux separately
('Resumed reading ... after a lag' 0.79s->4.82s).
Fix follows libobs video-io.c (https://github.com/obsproject/obs-studio)
- the video thread never resets its deadline; one frame per interval slot,
a late render repeats content (judder), never skips time:
1. FramePump: count-based emission, while(now>=nextTick){Submit; nextTick+=I}
2. FfmpegArgs: -re removed - the pump is the pacer
Muxed duration is now frame-count/fps == wall time by construction. Covers
game background + webcam (one shared pump). 0 warnings; suite 288/289 -
the one failure (Composite_FullScene_MasterPixels 1380,700) also fails with
this change stashed: pre-existing, untouched, recorded as follow-up.
This commit is contained in:
+41
-29
@@ -1,46 +1,58 @@
|
||||
# HANDOFF — 2026-09-08
|
||||
# HANDOFF — 2026-09-10
|
||||
|
||||
## Branch / Commit State
|
||||
|
||||
**`main` HEAD = `081e4c1`** — take-24 (PasteKey CropBounds). Working tree carries the
|
||||
real fix (uncommitted): pre-parse transparency injection + diagnostics.
|
||||
`main` HEAD = `1a39b09`, ahead of origin by 12, working tree carries THE recording-timing
|
||||
fix (uncommitted): count-based CFR emission in `FramePump.cs` + `-re` removal in
|
||||
`FfmpegArgs.cs`, plus this handoff / `ai.md` slice 9 / `MyMistakes.md` recipe correction.
|
||||
`tools/` holds `ticker.c` + the built `ticker` (WSL, unused by git yet).
|
||||
|
||||
## Web Overlay — the ROOT CAUSE (finally named, not guessed)
|
||||
## Recording TIMING — the fix (the "plays too fast" saga)
|
||||
|
||||
WebView2's `CapturePreviewAsync` **always honors the page's own background** — the widget
|
||||
page paints html/body opaque, so the captured PNG has **no alpha-0 margins, ever**.
|
||||
`DefaultBackgroundColor=Transparent` only shows through pages without a background style.
|
||||
All prior takes (19-24) assumed the capture had transparent margins — it never did.
|
||||
`FindContentBounds` therefore had nothing to crop to, and every blend saw alpha=255 =
|
||||
black box + "transparency broken" + lost resizing. One bug, three symptoms.
|
||||
**Root cause, proven by measurement (not guessed):** the pump's take-3 "rebase on overrun"
|
||||
reset `nextTick` to wall-now every time it fell behind, silently erasing missed slots. The
|
||||
pump delivered `215-219/300 per 5s` (~43fps) but ffmpeg muxes rawvideo by frame count at
|
||||
`-framerate 60` — no per-frame timestamps — so every recording played ~1.4x fast with
|
||||
stats that looked honest (a rebased frame is never "late"). `-re` on the demux was a second
|
||||
fighting pacer ("Resumed reading … after a lag" grew 0.79s→4.82s).
|
||||
|
||||
**Fix applied (verify by running):**
|
||||
1. `WebView2Manager.InitializeAsync`: `AddScriptToExecuteOnDocumentCreatedAsync(TransparentBackgroundScript)`
|
||||
— injects html/body `background:transparent` BEFORE the page parses/scripts run (the
|
||||
OBS user.css equivalent). NavStarting/NavCompleted keep the script as post-load re-assert.
|
||||
2. Restored the take-23/24 compositor crop path (stride-correct `BlitContentRaw` +
|
||||
`CropBounds` in PasteKey) — my previous take-25 gutting was wrong.
|
||||
3. Diagnostics: first capture per source dumps the RAW WebView2 PNG to
|
||||
`%TEMP%\ytLive-web-<id>.png` and logs alpha stats + FindContentBounds result to
|
||||
startup.log.
|
||||
**Fix (the OBS `video-io.c` shape — one frame per interval slot, deadline never reset):**
|
||||
1. `FramePump.PumpAsync`: `while (now >= nextTick) { SubmitFrame(frame); nextTick += intervalTicks; }`
|
||||
— one submit per crossed slot; a slow render re-writes the current composite (judder,
|
||||
never a skip). `nextTick` NEVER rebases to wall-now.
|
||||
2. `FfmpegArgs`: `-re` deleted from the rawvideo input; the pump is the pacer.
|
||||
|
||||
## NEXT STEP (user run required once)
|
||||
**Verified:** build 0 warnings; full suite 288/289 — the sole failure
|
||||
(`Composite_FullScene_MasterPixels` line 109, pixel (1380,700) cyan vs magenta) ALSO fails
|
||||
with this fix stashed, i.e. pre-existing and untouched by it. DO NOT fix it here; it is a
|
||||
separate compositor investigation.
|
||||
|
||||
Run the app with the web overlay, then:
|
||||
- Read `%APPDATA%\ytLlive\startup.log` — the `alpha[min=..,max=..,mean=..,zero=..%]`
|
||||
line PROVES whether the capture is transparent now (mean near 0 + high zero% + a tight
|
||||
contentBounds = fixed) or still opaque (mean ~255 → injection didn't beat the page).
|
||||
- Agent can PIL-analyze `%TEMP%\ytLive-web-<id>.png` for the true alpha bbox.
|
||||
## NEXT STEP (ONE user run required)
|
||||
|
||||
Record ~30s with the WSL ticker visible in the preview (`cd tools && ./ticker` in a terminal,
|
||||
Ctrl-C to stop), then check:
|
||||
- recording duration ≈ wall time (should be, by frame-count construction)
|
||||
- `%APPDATA%\ytLlive\startup.log` "FramePump stats:" shows ≈ n/n per 5s, n = 300 @60fps
|
||||
- NO "Resumed reading … after a lag" lines remain
|
||||
- ticker advances ~1s per second of footage
|
||||
|
||||
## Still Open
|
||||
|
||||
- Chat overlay missing from recording (separate issue — not yet touched this session)
|
||||
- Audio/sync: non-event (headset volume), closed
|
||||
- Web overlay: the pre-parse transparency injection (committed `1a39b09`) is NOT yet proven
|
||||
— both recorded diagnostic PNGs (`%TEMP%\ytLive-web-<id>.png`) were 100% alpha=0 AND
|
||||
RGB=0 (an EMPTY capture, not an opaque one). Diagnosis "capture is empty" needs a fresh
|
||||
run; the first-capture dump may be the pre-load about:blank frame. Frozen pending the
|
||||
timing verification.
|
||||
- Audio silence: `no audio.mp4` measured -91dB (digital silence), 3KiB muxed audio stream —
|
||||
the named-pipe audio delivered ~nothing. Separate from timing; not yet touched.
|
||||
- Pre-existing: `Composite_FullScene_MasterPixels` (see above).
|
||||
|
||||
## Landmines
|
||||
|
||||
- testhost shares startup.log with app — filter by time when triaging
|
||||
- testhost/exe lock DLLs: `taskkill /F /IM testhost.exe /IM ytLive.exe` before rebuild
|
||||
- Do NOT run full-suite vstest (WASAPI hang); flow = clean build + per-class + scope-check
|
||||
- Build via the Windows dotnet host: `/mnt/c/Program Files/dotnet/dotnet.exe build …`
|
||||
- Kill app before build: `/mnt/c/Windows/System32/taskkill.exe /F /IM ytLive.exe`
|
||||
- The PNG dump is written ONCE per session per source (DebugPngWritten flag)
|
||||
- No Linux ffmpeg / no sudo on this box — probing uses `/mnt/c/Program Files/Krita (x64)/bin/ffmpeg.exe`
|
||||
via WSL interop; frames landed in `/mnt/c/Users/gramp/AppData/Local/Temp/ylf/`
|
||||
- `tools/ticker`: constant ~+982ms offset on the FIRST line is cosmetic (t0 captured a beat late)
|
||||
Reference in New Issue
Block a user