c45cbc93b7
slice 9 made the DURATION right but content still hiccuped; aggregates (301/300, uniform file PTS) could not see it. Measured root cause: FfmpegEncoder.SubmitFrameAsync BLOCKED on WriteAsync(8.3MB)+FlushAsync when ffmpeg lagged the pipe, and the burst while-loop re-wrote that same stale composite per crossed slot — frozen runs. OBS shape (derivative, wrapped pre-1.0): the encoder queue in libobs/obs-encoder.c — encoder thread never couples back into the video thread; overflow = dropped data, never a frozen producer. https://github.com/obsproject/obs-studio/blob/master/libobs/obs-encoder.c - FfmpegEncoder: SubmitFrameAsync is now an enqueue (ArrayPool copy) into a bounded Channel (cap 120) drained by its own task; drop-newest + count when full; StopAsync flushes the queue then EOF (TryComplete). IFfmpegEncoder.DroppedFrames. - FramePump: ONE fresh composite per iteration (burst loop deleted); worst-submit stat, stall logger (>2x interval names the stage), dropped/stalls in stats. - Burned-in 6-digit dot-matrix frame counter (white box, bottom-right) on every composite — the clock-independent judge replacing the WSL ticker: +1/frame, jumps = counted drops. - ONE new test Backpressure_QueueOverflow_DropsFrames_AndNeverBlocks (slow-sink fake: submit never blocks, drops counted, stop flushes exactly submitted-minus-dropped). - Full suite 290 tests, 289 pass — sole failure the pre-existing compositor pixel test. - Docs same-commit: ai.md slice 10 (+ encoder/stop-note corrections), MyMistakes point 8, HANDOFF. Audio untouched (queued follow-up); web overlay still frozen pending timing closure.
69 lines
4.1 KiB
Markdown
69 lines
4.1 KiB
Markdown
# HANDOFF — 2026-09-10
|
||
|
||
## Branch / Commit State
|
||
|
||
`main` HEAD = `bd396e4` (committed this session: `d212d5a` tools ticker, `bd396e4` the
|
||
slice-9 CFR + `-re` removal). Now carries the **slice-10 reshape, UNCOMMITTED**:
|
||
bounded encoder queue + drop policy + burned-in frame counter in `FfmpegEncoder.cs` /
|
||
`IFfmpegEncoder.cs` / `FramePump.cs`, the one new backpressure test in
|
||
`ytLive.Tests/FfmpegEncoderTests.cs`, + `ai.md` slice-10 / `MyMistakes.md` point 8 /
|
||
this handoff. Working tree clean vs. the last commit **except** the slice-10 set.
|
||
|
||
## The timing saga — where it stands
|
||
|
||
- **slice 9 (committed `bd396e4`)** fixed the DURATION (count-based CFR, deadline never
|
||
rebased, `-re` removed): file length == wall time by frame-count construction.
|
||
- **But the CONTENT still hiccuped** — the creator read "1...23...4...56..." in the
|
||
recording, and the aggregates (301/300, uniform PTS, 15.6s wall vs 15.74s file) could
|
||
NOT see it. Root cause finally measured in `FfmpegEncoder.SubmitFrameAsync`: it BLOCKED
|
||
on `WriteAsync(8.3MB)+FlushAsync` when ffmpeg lagged the pipe, and slice 9's burst
|
||
`while` loop then re-wrote that SAME composite for every crossed slot — frozen runs.
|
||
- **slice 10 (current, uncommitted)** — the OBS `obs-encoder.c` shape:
|
||
1. `FfmpegEncoder.SubmitFrameAsync` is an ENQUEUE into a bounded `Channel<byte[]>`
|
||
(cap 120) drained by its own task; the pump NEVER blocks on the pipe.
|
||
2. Queue full → drop the NEWEST frame + count (`IFfmpegEncoder.DroppedFrames`).
|
||
Stop flushes the whole queue, then EOF.
|
||
3. Pump: ONE fresh composite per iteration (burst loop deleted) — no stale re-write.
|
||
4. **Burned-in frame counter** (the new judge, replaces the WSL ticker): 6-digit
|
||
dot-matrix strip, white box, bottom-right of every composite. Decoding the file
|
||
reads +1/frame; jumps = counted drops. Clock-independent.
|
||
5. Stats: `worst submit`, `dropped N`, `stalls K`; stall log names iterations > 2× interval.
|
||
|
||
## NEXT STEP (ONE user run required — the take that closes timing)
|
||
|
||
Record ~20s (WSL ticker visible in the preview is optional now — the burned counter is
|
||
the judge), then check:
|
||
- **decode the recording and read the bottom-right frame counter** (probe a few frames
|
||
widely spaced + the same region across a densely-sampled range): the number advances
|
||
**exactly +1 per frame**, jumping only where drops are countable
|
||
- `%APPDATA%\ytLlive\startup.log`: `FramePump stats:` ≈ n/n per 5s (n=300 @60fps),
|
||
`dropped 0`, `stalls 0`, `worst submit ≈ 1-3ms`
|
||
- NO "FramePump stall:" lines, NO frozen-content runs in the playback
|
||
- Extract a few frames around any suspicious moment and correlation-check the strip.
|
||
|
||
## Committed so far this session (before slice 10)
|
||
|
||
- `d212d5a` — tools: WSL ticker (`tools/ticker.c` + binary)
|
||
- `bd396e4` — fix(rec): CFR emission + `-re` removal + docs (ai.md slice 9, MyMistakes,
|
||
HANDOFF). Cites libobs video-io.c.
|
||
- Do NOT push until the user says (multi-commit local only).
|
||
|
||
## Still Open
|
||
|
||
- Web overlay: pre-parse transparency injection NOT yet proven — recorded diagnostic PNGs
|
||
(`%TEMP%\ytLive-web-<id>.png`) were 100% alpha=0 AND RGB=0 (an EMPTY capture). Frozen
|
||
pending the timing verification.
|
||
- Audio silence: silent audio (full-length, -91dB) in the take-15 file — the named-pipe
|
||
audio delivers ~nothing. Separate from timing; queued follow-up, NOT this change.
|
||
- WSL-ticker-under-heavy-load reliability check: informational (burned counter is judge).
|
||
- Pre-existing: `Composite_FullScene_MasterPixels` line 109 pixel (1380,700) — verbose
|
||
cyan-vs-magenta; fails with slice-9 stashed; untouched by both slices. Separate
|
||
compositor investigation. DO NOT fix in the timing work.
|
||
|
||
## 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
|
||
- Build via the Windows dotnet host: `/mnt/c/Program Files/dotnet/dotnet.exe build "C:\Users\gramp\Documents\Code\projects\ytLive\ytLive.csproj"`
|
||
- No Linux ffmpeg / no sudo on this box — probing uses `/mnt/c/Program Files/Krita (x64)/bin/ffmpeg.exe`
|
||
- `tools/ticker`: constant ~+982ms offset on the FIRST line is cosmetic (t0 a beat late) |