Files
LlamaCasty/HANDOFF.md
T
gramps c45cbc93b7 fix(rec): bounded encoder queue + drop policy + burned frame counter — the "1...23...4...56..." smeared-ticker take
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.
2026-09-10 09:32:55 -07:00

4.1 KiB
Raw Blame History

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)