Historical recipe — not current setup guidance. Measurements and implementation links below belong to their original workload and revision. Deployment labels and defaults may be obsolete. Use the reviewed performance recipes; browse historical evidence.

AVPlumber · Mixer optimization cookbook

Looped clips keep counting across the wrap

Implementation links are pinned to develop at c20464a5 (2026-10-09). The mixer has changed since; this page was checked against develop at 8fa15c92 on 2026-10-13. Measurements retain their original workload and scope. Watch the current 192-input L4 demo.

Current scope. Continuous loop timestamps remain useful with native-rate input pacing. References below to force_fps describe the historical normalized chain; current mixer file inputs feed realtime directly to the downstream mixer graph.

A looped file used to restart at its first timestamp on every pass, and each wrap cost the source a frame. With loop_continuous_ts, input_rec shifts each new pass by the length of the one before, so timestamps keep rising and a wrap looks like any other frame.

Status: Implemented
Timestamps of a 2 s clip looped for three passes, before and after Before: each pass runs from 0 to 2 s and then jumps back to 0; at each of the two wraps realtime resyncs and a frame is lost. After: pass 2 carries the file's timestamps plus 2 s and pass 3 plus 4 s, so the timestamps rise in one straight line from 0 to 6 s. Illustration · a 2 s clip looped for three passes Before · file timestamps After · loop_continuous_ts PTS 0 s 2 s 4 s 6 s 0 s 2 s 4 s 6 s pass 1 pass 2 pass 3 playback time +2 s +4 s resync resync before after
Illustration, not a measurement. Red dashed: every pass restarts at the file's first timestamp; at each wrap (red dot) realtime resyncs and the source loses a frame. Green: pass 2 carries the file's timestamps + 2 s and pass 3 + 4 s, so the line never steps back. In pass 1 the two lines are the same.

What went wrong at the wrap

File sources from a mixer config loop by default (--input URLs loop with --loop-inputs), and each generated source loops its own clip, 2 s long unless the recipe says otherwise (see Real 1080p inputs). At the end of the file input_rec seeks back to the start, and the next packets carry the file's first timestamps again.

realtime(set_pts) releases each frame when its timestamp is due. The first frame of a new pass looked one loop length overdue, so realtime resynced and released it at once, one frame period early. force_fps then got two frames for one output tick and dropped one: a stutter at every wrap, on every looped file source.

A mixer run with 2 s generated clips, measured when the change was made, showed it: force_fps estimated the input at 60.5 fps, dropped about 0.9 frames per second per source, and every wrap logged three warnings. These numbers come from that run, not from the smoke test below or the 110-source show.

How the shift works

  1. Measure each pass. For every packet, input_rec tracks the earliest timestamp and the latest timestamp + duration of the video stream, or of all streams in a file without video. It reads the PTS (the DTS when there is no PTS) and keeps the minimum and maximum over the whole pass, not the last packet's value, so B-frame reordering does not shorten the span. A packet without a duration counts as one frame at the stream's average frame rate, when the file declares one.
  2. Add the span at each forward wrap. Both loop points, end of file and stop_ts, go through loopForward(), which adds the finished pass's span to a running loop_offset_ and then seeks to start_ts. A 2 s clip at 25 fps runs from PTS 0 to 1.96 s, and its last frame lasts 0.04 s: span 2 s. Pass 2 is shifted by 2 s, pass 3 by 4 s.
  3. Shift every stream by the same offset. Just before a packet leaves input_rec, its PTS and DTS get loop_offset_. Audio gets the video's offset, so audio and video stay in sync.
  4. Control stays on file timestamps. stop_ts and seek-at entries compare the file's own timestamps, taken before the shift, and seek targets still resolve in the file. Only forward loops add to the offset; a reverse-playback loop adds nothing.

Where the mixer turns it on

At a glance

Where in the code

Markdown reference: NODES.md · input_rec documents loop_continuous_ts.

Source links are pinned to the reviewed revision.