matrice-streaming 0.1.73__py3-none-any.whl → 0.1.74__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -393,6 +393,16 @@ class StreamConfig:
393
393
  height: int = 640
394
394
  target_fps: int = 10
395
395
  gpu_id: int = 0
396
+ stream_type: str = "file" # "file" or "rtsp"
397
+
398
+ def __post_init__(self):
399
+ """Auto-detect stream_type from video_path if not explicitly set."""
400
+ if self.video_path.startswith("rtsp://") or self.video_path.startswith("rtsps://"):
401
+ self.stream_type = "rtsp"
402
+ elif self.video_path.startswith("http://") or self.video_path.startswith("https://"):
403
+ self.stream_type = "file" # Downloaded files
404
+ else:
405
+ self.stream_type = "file"
396
406
 
397
407
 
398
408
  @dataclass
@@ -424,6 +434,8 @@ class StreamState:
424
434
  empty_packets: int = 0
425
435
  decode_errors: int = 0 # Consecutive decode errors
426
436
  MAX_DECODE_ERRORS: int = 5 # Restart demuxer after this many consecutive errors
437
+ stream_type: str = "file" # "file" or "rtsp"
438
+ source_fps: float = 30.0 # Source video FPS for timestamp calculation
427
439
 
428
440
 
429
441
  # =============================================================================
@@ -620,11 +632,20 @@ class NVDECDecoderPool:
620
632
  logger.info(f"Created NVDEC pool: {self.actual_pool_size}/{pool_size} decoders on GPU {gpu_id}")
621
633
 
622
634
  def assign_stream(self, stream_id: int, camera_id: str, video_path: str,
623
- width: int = 640, height: int = 640) -> bool:
635
+ width: int = 640, height: int = 640,
636
+ stream_type: str = "file") -> bool:
624
637
  """Assign a stream to a decoder (round-robin).
625
638
 
626
639
  Automatically downloads HTTPS URLs to local files since PyNvVideoCodec's
627
640
  bundled FFmpeg doesn't support HTTPS protocol.
641
+
642
+ Args:
643
+ stream_id: Stream identifier
644
+ camera_id: Camera identifier
645
+ video_path: Video file path or RTSP URL
646
+ width: Target width
647
+ height: Target height
648
+ stream_type: Source type ("file" or "rtsp")
628
649
  """
629
650
  if self.actual_pool_size == 0:
630
651
  return False
@@ -641,23 +662,40 @@ class NVDECDecoderPool:
641
662
  logger.error(f"Failed to create demuxer for {camera_id}: {e}")
642
663
  return False
643
664
 
665
+ # Extract source FPS from demuxer for video timestamp calculation
666
+ source_fps = 30.0 # Default fallback
667
+ try:
668
+ # PyNvVideoCodec demuxer provides frame rate info
669
+ fps_num = demuxer.FrameRate()[0]
670
+ fps_den = demuxer.FrameRate()[1]
671
+ if fps_den > 0:
672
+ source_fps = fps_num / fps_den
673
+ logger.debug(f"{camera_id}: Detected source FPS = {source_fps:.2f}")
674
+ except Exception as e:
675
+ logger.debug(f"{camera_id}: Could not get FPS from demuxer, using default: {e}")
676
+
644
677
  stream_state = StreamState(
645
678
  stream_id=stream_id,
646
679
  camera_id=camera_id,
647
680
  video_path=local_path, # Store local path for video looping
648
681
  demuxer=demuxer,
649
682
  width=width,
650
- height=height
683
+ height=height,
684
+ stream_type=stream_type,
685
+ source_fps=source_fps,
651
686
  )
652
687
  self.streams_per_decoder[decoder_idx].append(stream_state)
653
688
  return True
654
689
 
655
690
  def decode_round(self, decoder_idx: int, frames_per_stream: int = 4,
656
- target_h: int = 640, target_w: int = 640) -> Tuple[int, List[Tuple[str, cp.ndarray]]]:
691
+ target_h: int = 640, target_w: int = 640) -> Tuple[int, List[Tuple[str, cp.ndarray, int, str]]]:
657
692
  """Decode frames and convert to NV12.
658
693
 
659
694
  Returns:
660
- (total_frames, [(camera_id, nv12_tensor), ...])
695
+ (total_frames, [(camera_id, nv12_tensor, timestamp_ns, stream_type), ...])
696
+ where:
697
+ - timestamp_ns: For RTSP = UTC nanoseconds, for files = video timestamp ns (frame_num / fps * 1e9)
698
+ - stream_type: "rtsp" or "file"
661
699
  """
662
700
  if decoder_idx >= self.actual_pool_size:
663
701
  return 0, []
@@ -676,6 +714,8 @@ class NVDECDecoderPool:
676
714
  if packet is None:
677
715
  stream.demuxer = nvc.CreateDemuxer(stream.video_path)
678
716
  stream.empty_packets = 0
717
+ # Reset frame counter on loop for consistent video timestamps
718
+ stream.frames_decoded = 0
679
719
  packet = stream.demuxer.Demux()
680
720
  if packet is None:
681
721
  break
@@ -685,10 +725,21 @@ class NVDECDecoderPool:
685
725
  # Wrap decode loop in try/except to catch decode errors
686
726
  try:
687
727
  for surface in decoder.Decode(packet):
728
+ # Calculate timestamp based on stream type:
729
+ # - RTSP: UTC nanoseconds for real-time sync
730
+ # - File: Video timestamp (frame_num / fps * 1e9) for video playback sync
731
+ if stream.stream_type == "rtsp":
732
+ decode_timestamp_ns = time.time_ns()
733
+ else:
734
+ # Video file: use video timestamp based on frame number
735
+ # This allows syncing to the video timeline instead of wall clock
736
+ video_time_seconds = stream.frames_decoded / stream.source_fps
737
+ decode_timestamp_ns = int(video_time_seconds * 1_000_000_000)
738
+
688
739
  tensor = surface_to_nv12(surface, target_h, target_w)
689
740
 
690
741
  if tensor is not None:
691
- decoded_frames.append((stream.camera_id, tensor))
742
+ decoded_frames.append((stream.camera_id, tensor, decode_timestamp_ns, stream.stream_type))
692
743
  frames_this_stream += 1
693
744
  stream.frames_decoded += 1
694
745
  total_frames += 1
@@ -839,7 +890,7 @@ def nvdec_pool_worker(
839
890
  target_w=TARGET_WIDTH # Always 640
840
891
  )
841
892
 
842
- for cam_id, tensor in decoded_frames:
893
+ for cam_id, tensor, decode_timestamp_ns, stream_type in decoded_frames:
843
894
  if cam_id in ring_buffers:
844
895
  try:
845
896
  # Validate frame shape matches expected NV12 dimensions
@@ -853,7 +904,11 @@ def nvdec_pool_worker(
853
904
  )
854
905
  continue # Skip this frame
855
906
 
856
- ring_buffers[cam_id].write_frame_fast(tensor, sync=False)
907
+ # Pass decode-time timestamp for accurate frame timing
908
+ # For RTSP: UTC nanoseconds, for files: video timestamp ns
909
+ ring_buffers[cam_id].write_frame_fast(
910
+ tensor, sync=False, timestamp_ns=decode_timestamp_ns
911
+ )
857
912
  local_frames += 1
858
913
  frames_since_counter_update += 1
859
914
 
@@ -880,7 +935,7 @@ def nvdec_pool_worker(
880
935
  # Sync all buffers that received frames in this round
881
936
  # Critical for cross-container IPC - ensures GPU writes are visible
882
937
  synced_cams = set()
883
- for cam_id, _ in decoded_frames:
938
+ for cam_id, _, _, _ in decoded_frames:
884
939
  if cam_id in ring_buffers and cam_id not in synced_cams:
885
940
  ring_buffers[cam_id].sync_writes()
886
941
  synced_cams.add(cam_id)
@@ -1047,7 +1102,8 @@ def nvdec_pool_process(
1047
1102
  camera_id=config.camera_id,
1048
1103
  video_path=config.video_path,
1049
1104
  width=TARGET_WIDTH, # Use fixed width
1050
- height=TARGET_HEIGHT # Use fixed height
1105
+ height=TARGET_HEIGHT, # Use fixed height
1106
+ stream_type=config.stream_type, # Pass stream type for timestamp mode
1051
1107
  )
1052
1108
 
1053
1109
  # Write all GPU mappings at once
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrice_streaming
3
- Version: 0.1.73
3
+ Version: 0.1.74
4
4
  Summary: Common server utilities for Matrice.ai services
5
5
  Author-email: "Matrice.ai" <dipendra@matrice.ai>
6
6
  License-Expression: MIT
@@ -33,7 +33,7 @@ matrice_streaming/streaming_gateway/camera_streamer/gstreamer_camera_streamer.py
33
33
  matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker.py,sha256=AqKNJ6q_BxFphOlJ2GaS4WpoLCHXLEu5JVvoKQNrGV0,42822
34
34
  matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker_manager.py,sha256=jlKwIWWMXpztdyKiyremGmkVyw9mf2AxEmT7154xnrc,22002
35
35
  matrice_streaming/streaming_gateway/camera_streamer/message_builder.py,sha256=W295q6cIm05ReF1ooQus3rsKgZOG3EldZplbQco-OyM,10231
36
- matrice_streaming/streaming_gateway/camera_streamer/nvdec.py,sha256=A94bIwBC9rX7AJUdNYm9KRoKgiVEgSI5z_0zMpeQZ7g,58503
36
+ matrice_streaming/streaming_gateway/camera_streamer/nvdec.py,sha256=Eumbrv0XVxuwqUiSZSxylWFph_3vsvYd-BK_zg61Wqc,61651
37
37
  matrice_streaming/streaming_gateway/camera_streamer/nvdec_worker_manager.py,sha256=KlcwKFUPVZTQ3J1VIuhPev8Xv9BNw4dj2iLGHrREQCQ,16035
38
38
  matrice_streaming/streaming_gateway/camera_streamer/platform_pipelines.py,sha256=UNjsYYWbUJteOq2tzxISFAbWMa2e9GUExSS6fWc2Aow,27303
39
39
  matrice_streaming/streaming_gateway/camera_streamer/retry_manager.py,sha256=d8tlGoWoeSlgpCgXbUHTM61ekCQZki7TO1HzL2yPVzk,3607
@@ -49,8 +49,8 @@ matrice_streaming/streaming_gateway/debug/debug_streaming_gateway.py,sha256=ZiDg
49
49
  matrice_streaming/streaming_gateway/debug/debug_utils.py,sha256=jWcSBgrk_YVt1QzSyw6geX17YBnTvgVdA5ubqO531a0,10477
50
50
  matrice_streaming/streaming_gateway/debug/example_debug_streaming.py,sha256=-gS8zNDswAoj6oss66QQWYZhY24usfLiMH0FFK06vV0,7994
51
51
  matrice_streaming/streaming_gateway/debug/test_videoplayback.py,sha256=s_dgWkoESiuJHlUAf_iv4d7OGmAhwocwDZmIcFUZzvo,11093
52
- matrice_streaming-0.1.73.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
53
- matrice_streaming-0.1.73.dist-info/METADATA,sha256=s5imC3p-Su6FbgN7sNKjQ-78GgbZtmhiO280weJuj08,2477
54
- matrice_streaming-0.1.73.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
- matrice_streaming-0.1.73.dist-info/top_level.txt,sha256=PM_trIe8f4JLc90J871rNMYGVM3Po9Inx4As5LrCFUU,18
56
- matrice_streaming-0.1.73.dist-info/RECORD,,
52
+ matrice_streaming-0.1.74.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
53
+ matrice_streaming-0.1.74.dist-info/METADATA,sha256=5-ineu029FfwWA0JQszQXYX8zt9uUsyS-zYhrl6ThZA,2477
54
+ matrice_streaming-0.1.74.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
+ matrice_streaming-0.1.74.dist-info/top_level.txt,sha256=PM_trIe8f4JLc90J871rNMYGVM3Po9Inx4As5LrCFUU,18
56
+ matrice_streaming-0.1.74.dist-info/RECORD,,