Skip to content

PTP sync

Engine code: src/sync/ptp_clock.* (reference sources, engine clock), src/sync/drift_model.*, src/sync/group_phase.* + present_timing.*.

  • Frame n is due at n / fps on the shared clock (TAI ns for PTP): every node configured with the same refresh rate shows the same frame index at the same PTP time; ApplyAt.ptp_time_ns and MediaClock.ptp_time_ns are in that time base (docs/engine/media-clock.md).
  • A sync thread reads the reference 8× per second, bracketed by two CLOCK_MONOTONIC reads, and fits a drift model (64 samples). All other threads read the engine clock through the model (the PHC is never read on the frame path).
  • PTP lost (ptp4l reports no grandmaster / offset > 1 ms, PHC unreadable, kernel clock unsynchronised): SyncStatus { quality: UNLOCKED }, HealthStatus unchanged otherwise; the engine free-runs on the last drift.
  • Re-lock / reference step: the offset between the free-run estimate and the reference is not jumped to; it is applied at the next layer cut (any transport change) in one go, or — if no cut comes within 2 s — in steps of at most one frame per frame (a forward step skips a frame, a backward step holds the clock for a frame: engine time never runs backwards). Each step increments SyncStatus.corrections (with the output-group rate / phase corrections). Offsets above 1 s at the first lock (time base established late) restart the frame clock.
  • SyncStatus events (topic sync): on quality changes, on each correction, and every second. Fields: source (ptp / ntp / local), quality, offset_us (ptp4l offset to the grandmaster + pending correction), drift_ppm (reference vs. local oscillator), corrections.

[sync] in /etc/freedome-engined/config.toml

Section titled “[sync] in /etc/freedome-engined/config.toml”
source Linux Windows / macOS
auto (default) PHC ptp_device when ptp4l answers on /var/run/ptp4l; else CLOCK_TAI when the kernel clock is synchronised and the TAI offset is set (phc2sys); else local ntp
ptp PHC ptp_device (default /dev/ptp0) if it exists, else CLOCK_TAI NTP fallback, COARSE
ntp CLOCK_REALTIME (chrony / ntpd / systemd-timesyncd), COARSE system time (w32time / timed), COARSE — documented limit ≤ 5 ms
local CLOCK_MONOTONIC, single node, LOCKED same

Lock state with the PHC comes from ptp4l’s management socket (TIME_STATUS_NP + DEFAULT_DATA_SET, like pmc -u -b 0 'GET TIME_STATUS_NP'): LOCKED when a grandmaster is present and |offset| < 1 ms, or when this node is the grandmaster. The engine user must be able to open /dev/ptpN (group ptp / udev rule below) and to write to /var/run/ptp4l (ptp4l creates it 0660 root:root by default — see uds_file_mode / group below).

freedome-engined Recommends: linuxptp (Debian / Astra: linuxptp; RED OS / Kylin RPM: linuxptp). The engine package does not start PTP itself; the media server’s node provisioning enables the units.

/etc/linuxptp/ptp4l.conf (excerpt; one PTP-capable NIC per node, hardware timestamping):

[global]
time_stamping hardware
domainNumber 0 ; the engine's management client queries domain 0
uds_address /var/run/ptp4l
uds_file_mode 0660 ; linuxptp ≥ 4.0: let group `freedome` query TIME_STATUS_NP
clock_servo pi
step_threshold 0.0 ; never step the PHC after the first lock: the engine applies steps itself
first_step_threshold 0.00002
[enp1s0]

Units (systemd, linuxptp ships [email protected] / [email protected]):

Terminal window
systemctl enable --now [email protected]
# System clock from the PHC (for logs, NTP-free nodes, and CLOCK_TAI when no PHC is configured in the engine):
# -w waits for ptp4l, -a -r follows the port state, the kernel TAI offset is set from ptp4l's UTC offset.
systemctl enable --now [email protected] # ExecStart=phc2sys -a -r -w

freedome-engined.service already orders after ptp4l.service; for the templated unit add a drop-in:

/etc/systemd/system/freedome-engined.service.d/ptp.conf
[Unit]
[Service]
SupplementaryGroups=video render ptp

Device access (udev, shipped by the package as 60-freedome-ptp.rules):

SUBSYSTEM=="ptp", GROUP="ptp", MODE="0660"

and the ptp group created by sysusers.d (g ptp - -), with /var/run/ptp4l given to that group (ExecStartPost=/bin/chgrp ptp /var/run/ptp4l in a ptp4l drop-in, or uds_file_mode + SupplementaryGroups).

Containers (freedome/engined): pass --device /dev/ptp0 and mount /var/run/ptp4l from the host; ptp4l runs on the host.

Terminal window
pmc -u -b 0 'GET TIME_STATUS_NP' # gmPresent true, master_offset in ns
phc_ctl /dev/ptp0 cmp # PHC vs CLOCK_REALTIME (≈ 37 s TAI–UTC once phc2sys runs)
freedome-engine-cli events --topics sync # source ptp, quality LOCKED, offset_us, drift_ppm
  • CI: two freedome-engined instances with skewed simulated oscillators on one simulated PTP time base (FREEDOME_SYNC_SIMULATE, test use only) → identical frame grid and identical scheduled-play frame (tests/grpc_conformance/test_sync.py); loss / re-lock / step → UNLOCKED, LOCKED, ±1-frame corrections within 2 s (unit tests tests/unit/sync_ptp_clock.cpp and the conformance test).
  • Blocked-hardware 🔧: two Linux nodes with PTP-capable NICs (i210 / i225 / E810 class) + photodiode or camera on both outputs: ≤ 1 ms clock offset, 0 frame skew; Windows / macOS COARSE ≤ 5 ms on dedicated runners.