Skip to content

BufferedCapture: never let a bad PTS kill the pipeline at segment rollover - #973

Draft
Cybis320 wants to merge 2 commits into
CroatianMeteorNetwork:prereleasefrom
Cybis320:fix-segment-naming-bad-pts
Draft

BufferedCapture: never let a bad PTS kill the pipeline at segment rollover#973
Cybis320 wants to merge 2 commits into
CroatianMeteorNetwork:prereleasefrom
Cybis320:fix-segment-naming-bad-pts

Conversation

@Cybis320

Copy link
Copy Markdown
Contributor

Symptom

Roughly 30s after a pipeline start that follows a camera-settings or protocol change, capture dies with what looks like a disconnect:

GStreamer pipeline did not emit a sample.
Frame grabbing failed, video device is probably disconnected!

The camera is fine. The console (not the RMS log) shows what actually happened:

filesink     gst_file_sink_open_file:<sink_1>  error: No file name specified for writing.
basesink     gst_base_sink_change_state:<sink_1> error: Failed to start
splitmuxsink start_next_fragment:<splitmuxsink0> error: Could not start new output sink
GST ERROR from sink_1: state change failed

Cause

start_next_fragment is only reachable at a segment rollover, and <sink_1> is the second fragment, so the earliest this can fire is one raw_video_duration (30s) in. The 30s is the segment length, not a timeout.

moveSegment is connected to format-location-full and is splitmuxsink's only source of a filename — no location is set on the element. When the callback returns NULL, splitmuxsink hands its internal filesink an empty location, the filesink fails to open, and that error goes to the bus as a pipeline-level failure. The appsink then stops emitting and RMS reports it as a disconnect.

The callback returned NULL because it raised. It derived the segment name straight from the first sample's PTS with no sanity check:

segment_timestamp = self.start_timestamp + (buffer.pts + self.last_pts_correction_ns)/1e9
segment_time = UTCFromTimestamp.utcfromtimestamp(segment_timestamp)

A camera restarting its RTP timebase (which is exactly what a settings or protocol change does) can emit a wild PTS for the first seconds, giving an out-of-range epoch that utcfromtimestamp rejects. getFrame already guards this at the same file, line 590 — moveSegment did not. It also had no try/except, so any exception was fatal to the pipeline.

This is self-clearing: RMS reconnects, PTS state is reset, and the rest of the night is fine. It costs the first ~30s of video and one pipeline restart.

Fix

  • MAX_EXPECTED_PTS_NS promoted to a module constant, shared with the existing getFrame check
  • new segmentTimestamp() validates the PTS against that bound, and additionally rejects a derived time more than a day from now (which also catches start_timestamp never having been established, since it initializes to 0)
  • moveSegment catches anything raised while deriving the time
  • when no trustworthy time can be established, the clip is still saved, under <STATIONID>_UNKNOWNTIME_<fragment>_video.mkv — a name that makes no claim rather than a wrong one — and logged at warning, so it appears in the RMS log instead of only on stderr
  • the directory still comes from the wall clock, so the clip is filed with its neighbours

last_segment_savetime is removed: it existed only for the old wall-clock fallback and is now write-only.

The callback deliberately still returns None if the video directory itself cannot be created. That errors the pipeline, which is correct — nothing survives an unwritable data dir, and it is better than dropping clips somewhere that is never cleaned up.

Cleanup

UNKNOWNTIME clips age out exactly like normal ones. DeleteOldObservations.getRawItems(..., in_video_dir=True) collects day directories, not filenames, and deletes them wholesale. Verified against the real cleanup code: a directory holding one normal and one UNKNOWNTIME clip is removed completely, no leftovers.

The only consumer that parses these names is FrameInterface when a clip is opened by hand in SkyFit2. It reports that the start time cannot be read from the file name, which is the honest outcome — and the name still has four _-separated fields, so the existing ValueError handler catches it rather than an IndexError escaping.

Testing

moveSegment exercised against stubs for every failure mode. All return a usable path except the unwritable-directory case:

case result
good PTS NZ005F_20260822_171411_752730_video.mkv
PTS = CLOCK_TIME_NONE UNKNOWNTIME
PTS > 24h (wild timebase) UNKNOWNTIME
PTS = 0 UNKNOWNTIME
no sample at all UNKNOWNTIME
get_buffer() raises UNKNOWNTIME
start_timestamp never set UNKNOWNTIME
no sample, last_running_time_ns known normal timestamped name
video dir unwritable None (pipeline errors, by design)

🤖 Generated with Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant