fix(portal): convert PipeWire nanosecond PTS to encoder frame-number units

PipeWire spa_meta_header.pts is CLOCK_MONOTONIC in nanoseconds, but the
encoder expects frame-number units (time_base = 1/fps). The raw nanosecond
value was assigned directly to AVFrame.pts, causing the encoder/muxer to
interpret timestamps as billions of frames, producing corrupted duration
metadata and broken rate control.

Fix: record the first frame's PTS as a nanosecond base, compute elapsed
nanoseconds for each subsequent frame, then convert to frame numbers via
elapsed_ns * fps / 1_000_000_000. Using elapsed time avoids i64 overflow
on absolute timestamps (~10^18 ns).

Matches the WLR path pattern (state.rs:525-527) which converts microseconds
to frame numbers for the same encoder.
This commit is contained in:
dailz
2026-05-22 13:05:53 +08:00
parent 75a57e43ec
commit ffb36b7e0d

View File

@@ -25,6 +25,7 @@ pub struct StatePortal {
errored: bool,
first_frame: bool,
drm_device: PathBuf,
first_pts_ns: Option<i64>,
}
impl StatePortal {
@@ -43,6 +44,7 @@ impl StatePortal {
errored: false,
first_frame: true,
drm_device,
first_pts_ns: None,
})
}
@@ -174,9 +176,16 @@ impl StatePortal {
bail!("av_hwframe_transfer_data failed: error {ret}");
}
// 7. Set PTS
// 7. Set PTS — convert PipeWire nanoseconds to encoder frame-number units
// PipeWire PTS is CLOCK_MONOTONIC in nanoseconds.
// Encoder time_base = 1/fps, so PTS must be in frame numbers.
// Use elapsed time since first frame to avoid i64 overflow on absolute timestamps.
let fps_i64 = self.args.fps as i64;
let base_ns = *self.first_pts_ns.get_or_insert(frame.pts.max(0));
let elapsed_ns = (frame.pts.max(0) - base_ns).max(0);
let pts = elapsed_ns * fps_i64 / 1_000_000_000;
unsafe {
(*hw_frame.as_mut_ptr()).pts = frame.pts;
(*hw_frame.as_mut_ptr()).pts = pts;
}
// 8. Encode