Skip to content

Fractional stream timing

Capture intervals and encoder timebases share the requested rational stream rate. Legacy integral rates and the existing whole-frame budgeting fields stay available; they no longer determine fractional capture cadence.

Polaris/Nova’s explicit millihertz requests remain exact. A request of 59940 represents 2997/50 Hz. It is not silently changed to 60000/1001. For the standard GameStream whole-FPS request, an optional x-nv-video[0].clientRefreshRateX100 hint can supply fractional precision when it lies strictly within the requested whole rate’s rounding interval. Standard NTSC hints normalize to their rational rates, including 5994 to 60000/1001 and 2397/2398 to 24000/1001. A 12000 display hint accompanying a 60 FPS stream is ignored. Warp requests and launch-display rates retain their separate meaning; the encoder limiter uses the launch rate only when the existing limit setting calls for it.

PipeWire capture records the requested rate as part of capture reuse identity and logs both requested and negotiated rates. Its initial format offer uses variable framerate and a maxFramerate range. KWin versions before 6.8 prefer variable capture; confirmed 6.8 and newer prefer the requested rate. An unknown version keeps the variable policy. The version query has an owner-scoped cancellable and a single 500 ms deadline covering D-Bus acquisition and query.

Matching compatibility offers follow every primary format, accepting fixed-only producers through a framerate range without maxFramerate. Primary offers retain literal variable framerate so a producer’s preferred fixed rate cannot displace variable capture when both are available. Both passes preserve format ordering, modifiers, and HDR/SDR constraints.

A producer that rejects the maximum-rate offer before negotiating may retry once using a legacy framerate-only offer. Retry retains the same node, core, formats, HDR policy, and DMA-BUF restrictions. It rechecks stop, negotiation, and launch cancellation after acquiring the PipeWire loop, before replacing the stream. Session teardown interrupts startup negotiation before waiting for its start owner to release the admission fence.

Fixed negotiated rates take precedence over a variable maximum. A producer running at the requested rate or slower delivers frames through the existing event-driven wait. Faster or variable production uses a cancellable deadline in the consumer wait, retaining the latest available frame; the PipeWire callback thread never sleeps to pace capture. Lower negotiated rates are reported rather than silently relabeled. SPA buffers explicitly satisfy eight-byte alignment.

Regression coverage includes integer/NTSC/explicit-millihertz rates, inconsistent client display hints, Warp and launch limits, generated SPA properties, actual PipeWire loop contention during stop and retry, owner cancellation, teardown fences, and KWin query cancellation/deadline. Native and sanitizer results are recorded with the candidate. Physical producer negotiation and measured capture cadence are additional acceptance evidence, not implied by these tests.

tools/tests/pipewire_rate_harness.py starts a private PipeWire graph and a GStreamer synthetic producer, then measures 181 frames through the actual native capture consumer. It removes its processes and private runtime on exit. A Linux run measured 60.0008 FPS from a fixed 60 Hz producer, 59.9993 FPS for a 60 FPS request from a fixed 120 Hz producer, and 59.9423 FPS for 60000/1001. These results validate synthetic producer negotiation and consumer pacing; they do not establish desktop-compositor cadence or game-stream performance.