Explorar el Código

fix(camera): P2S RTSP stream dropped every frame after the first (#1395)

  A fresh P2S support bundle showed ffmpeg's reason for the stall:
  `frame=1 time=00:00:00.06 dup=0 drop=526 speed=0.0037x`. ffmpeg
  connects and frames arrive (drop counter climbs ~15/s), but it emits
  one output frame and the output clock freezes.

  The streaming command ends with `-r 15`, putting ffmpeg in CFR mode:
  it drops/dupes input frames to hit 15 fps based on the source's
  timestamps. P2S firmware 01.02.00.00 sends an RTSP stream whose RTP
  timestamps don't advance, so CFR treats every frame after the first
  as a same-timestamp duplicate and drops it. Snapshot capture works on
  the same printer because that path has no `-r` (no CFR conversion);
  X1/H2 are unaffected because their firmware timestamps are correct.
  The earlier probesize fix was masking this second bug.

  Add `-use_wallclock_as_timestamps 1` to the P2S camera profile via
  the existing extra_ffmpeg_input_args hook. ffmpeg rebuilds each
  packet's PTS from arrival wall-clock time, the output clock advances,
  and CFR conversion works. No dataclass change, no other model touched.

  Tests: 2 new in test_camera_profiles.py (P2S splices the flag+value
  pair; default profile keeps extra_ffmpeg_input_args empty so the
  override never leaks to X1/H2).
maziggy hace 5 días
padre
commit
6d7a92c024

La diferencia del archivo ha sido suprimido porque es demasiado grande
+ 0 - 0
CHANGELOG.md


+ 15 - 4
backend/app/services/camera_profiles.py

@@ -77,13 +77,24 @@ DEFAULT_PROFILE = CameraProfile()
 # AFTER alias normalisation, so internal SSDP codes ("N7") resolve via
 # AFTER alias normalisation, so internal SSDP codes ("N7") resolve via
 # ``_MODEL_ALIASES`` below.
 # ``_MODEL_ALIASES`` below.
 _PROFILES: dict[str, CameraProfile] = {
 _PROFILES: dict[str, CameraProfile] = {
-    # P2S firmware 01.02.00.00 RTSP keyframe pacing is slow enough that
-    # ffmpeg's "32-byte probe + zero analyze" combo can't estimate the
-    # frame rate. ffmpeg's own stderr literally says "consider increasing
-    # probesize" (#1395 follow-up).
+    # P2S firmware 01.02.00.00 has two RTSP quirks, both surfaced by #1395:
+    #
+    # 1. Slow keyframe pacing — ffmpeg's "32-byte probe + zero analyze"
+    #    combo can't estimate the frame rate ("consider increasing
+    #    probesize"). Fixed by the relaxed probesize/analyzeduration below.
+    #
+    # 2. Non-advancing RTP timestamps — every frame is stamped at ~t=0.06s.
+    #    With ffmpeg's default CFR rate conversion (`-r 15`), this freezes
+    #    the output clock after the first frame and drops every subsequent
+    #    frame as a same-timestamp duplicate (ffmpeg stderr: `frame=1
+    #    time=00:00:00.06 dup=0 drop=526`). `-use_wallclock_as_timestamps 1`
+    #    regenerates each packet's PTS from arrival wall-clock time, so the
+    #    output clock advances and CFR conversion works. X1/H2 send correct
+    #    timestamps and need no override.
     "P2S": CameraProfile(
     "P2S": CameraProfile(
         probesize=1_000_000,
         probesize=1_000_000,
         analyzeduration=500_000,
         analyzeduration=500_000,
+        extra_ffmpeg_input_args=("-use_wallclock_as_timestamps", "1"),
     ),
     ),
 }
 }
 
 

+ 18 - 0
backend/tests/unit/services/test_camera_profiles.py

@@ -50,6 +50,24 @@ class TestGetCameraProfile:
         assert profile.probesize >= 1_000_000
         assert profile.probesize >= 1_000_000
         assert profile.analyzeduration >= 500_000
         assert profile.analyzeduration >= 500_000
 
 
+    def test_p2s_regenerates_timestamps_from_wallclock(self):
+        """P2S firmware 01.02.00.00 sends non-advancing RTP timestamps;
+        ffmpeg's default CFR conversion (`-r 15`) then freezes the output
+        clock after frame 1 and drops everything else (#1395). The profile
+        must splice `-use_wallclock_as_timestamps 1` into the input args so
+        ffmpeg rebuilds PTS from arrival time. Order matters — the flag and
+        its value must be adjacent so they reach ffmpeg as a pair."""
+        args = get_camera_profile("P2S").extra_ffmpeg_input_args
+        assert "-use_wallclock_as_timestamps" in args
+        idx = args.index("-use_wallclock_as_timestamps")
+        assert args[idx + 1] == "1"
+
+    def test_default_profile_has_no_timestamp_override(self):
+        """X1/H2 send correct, advancing RTP timestamps — they must NOT get
+        the wallclock override, which would needlessly re-stamp a healthy
+        stream. Guards against the P2S fix leaking into the default."""
+        assert DEFAULT_PROFILE.extra_ffmpeg_input_args == ()
+
     def test_p2s_internal_code_resolves_to_p2s_profile(self):
     def test_p2s_internal_code_resolves_to_p2s_profile(self):
         """SSDP internal codes (e.g. `N7` for P2S) must resolve to the
         """SSDP internal codes (e.g. `N7` for P2S) must resolve to the
         same profile as their display name. Otherwise printers freshly
         same profile as their display name. Otherwise printers freshly

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio