matrice-streaming 0.1.72__tar.gz → 0.1.74__tar.gz
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.
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/PKG-INFO +1 -1
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/matrice_streaming.egg-info/PKG-INFO +1 -1
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/async_camera_worker.py +46 -13
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/nvdec.py +65 -9
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/LICENSE.txt +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/README.md +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/matrice_streaming.egg-info/SOURCES.txt +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/matrice_streaming.egg-info/dependency_links.txt +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/matrice_streaming.egg-info/not-zip-safe +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/matrice_streaming.egg-info/top_level.txt +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/pyproject.toml +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/setup.cfg +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/setup.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/__init__.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/client/__init__.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/client/client.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/client/client_utils.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/__init__.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/camera_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/deployment.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/inference_pipeline.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/streaming_gateway_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/todo.txt +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/py.typed +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/__init__.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/ARCHITECTURE.md +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/__init__.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/async_ffmpeg_worker.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/camera_streamer.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/device_detection.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/encoder_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/encoding_pool_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_camera_streamer.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_config.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_worker_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/frame_processor.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/gstreamer_camera_streamer.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/message_builder.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/nvdec_worker_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/platform_pipelines.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/retry_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/stream_statistics.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/video_capture_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/camera_streamer/worker_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/README.md +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/__init__.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/benchmark.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/debug_gstreamer_gateway.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/debug_stream_backend.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/debug_streaming_gateway.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/debug_utils.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/example_debug_streaming.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/debug/test_videoplayback.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/dynamic_camera_manager.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/event_listener.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/metrics_reporter.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/streaming_action.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/streaming_gateway.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/streaming_gateway_utils.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/streaming_gateway/streaming_status_listener.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_async_infrastructure.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_batch_auto_calculation.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_batching_verification.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_e2e_production.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_flatten_binary.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_gstreamer_integration.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_msgpack_fix.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_phase1_unit.py +0 -0
- {matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/tests/test_phase2_scaling.py +0 -0
|
@@ -495,6 +495,9 @@ class AsyncCameraWorker:
|
|
|
495
495
|
cap = None
|
|
496
496
|
consecutive_failures = 0
|
|
497
497
|
frame_counter = 0
|
|
498
|
+
# PTS-based pacing for video files
|
|
499
|
+
first_video_ms = None
|
|
500
|
+
first_wall_time = None
|
|
498
501
|
|
|
499
502
|
try:
|
|
500
503
|
# Prepare source (download if URL)
|
|
@@ -570,6 +573,18 @@ class AsyncCameraWorker:
|
|
|
570
573
|
consecutive_failures = 0
|
|
571
574
|
frame_counter += 1
|
|
572
575
|
|
|
576
|
+
# Get video file timestamp (for video files, use video PTS instead of wall clock)
|
|
577
|
+
# This prevents timestamp jumps when pipeline stalls
|
|
578
|
+
video_ts_ms = cap.get(cv2.CAP_PROP_POS_MSEC) if source_type == "video_file" else None
|
|
579
|
+
|
|
580
|
+
# Frame decimation: Read ALL frames, skip based on target FPS
|
|
581
|
+
# This avoids sleep-based throttling which misses frames
|
|
582
|
+
source_fps = video_props.get('original_fps', 30) if video_props else 30
|
|
583
|
+
if fps > 0 and source_fps > fps:
|
|
584
|
+
skip_ratio = int(source_fps / fps)
|
|
585
|
+
if skip_ratio > 1 and frame_counter % skip_ratio != 0:
|
|
586
|
+
continue # Skip this frame, read next immediately
|
|
587
|
+
|
|
573
588
|
# Resize if needed
|
|
574
589
|
if width or height:
|
|
575
590
|
frame = FrameProcessor.resize_frame(frame, width, height)
|
|
@@ -581,7 +596,7 @@ class AsyncCameraWorker:
|
|
|
581
596
|
await self._process_frame_shm_mode(
|
|
582
597
|
frame, stream_key, stream_group_key, topic,
|
|
583
598
|
actual_width, actual_height, frame_counter,
|
|
584
|
-
camera_location, read_time
|
|
599
|
+
camera_location, read_time, video_ts_ms
|
|
585
600
|
)
|
|
586
601
|
else:
|
|
587
602
|
# EXISTING FLOW: JPEG encode and send full frame
|
|
@@ -589,16 +604,22 @@ class AsyncCameraWorker:
|
|
|
589
604
|
frame, stream_key, stream_group_key, topic,
|
|
590
605
|
source, video_props, fps, quality,
|
|
591
606
|
actual_width, actual_height, source_type,
|
|
592
|
-
frame_counter, camera_location, read_time
|
|
607
|
+
frame_counter, camera_location, read_time,
|
|
608
|
+
video_ts_ms
|
|
593
609
|
)
|
|
594
610
|
|
|
595
|
-
#
|
|
596
|
-
#
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
611
|
+
# PTS-based pacing for video files (smooth playback without drift)
|
|
612
|
+
# Instead of fixed-interval sleep, pace based on video timeline
|
|
613
|
+
if source_type == "video_file" and video_ts_ms is not None:
|
|
614
|
+
if first_video_ms is None:
|
|
615
|
+
first_video_ms = video_ts_ms
|
|
616
|
+
first_wall_time = time.time()
|
|
617
|
+
else:
|
|
618
|
+
# Calculate target wall time based on video PTS
|
|
619
|
+
target_wall = first_wall_time + (video_ts_ms - first_video_ms) / 1000.0
|
|
620
|
+
sleep_time = target_wall - time.time()
|
|
621
|
+
if sleep_time > 0:
|
|
622
|
+
await asyncio.sleep(sleep_time)
|
|
602
623
|
|
|
603
624
|
except asyncio.CancelledError:
|
|
604
625
|
self.logger.info(f"Worker {self.worker_id}: Camera {stream_key} task cancelled")
|
|
@@ -671,7 +692,8 @@ class AsyncCameraWorker:
|
|
|
671
692
|
source_type: str,
|
|
672
693
|
frame_counter: int,
|
|
673
694
|
camera_location: str,
|
|
674
|
-
read_time: float
|
|
695
|
+
read_time: float,
|
|
696
|
+
video_ts_ms: Optional[float] = None
|
|
675
697
|
):
|
|
676
698
|
"""Process frame and send to Redis asynchronously.
|
|
677
699
|
|
|
@@ -692,6 +714,7 @@ class AsyncCameraWorker:
|
|
|
692
714
|
frame_counter: Current frame number
|
|
693
715
|
camera_location: Camera location
|
|
694
716
|
read_time: Time taken to read frame
|
|
717
|
+
video_ts_ms: Video file timestamp in milliseconds (None for live streams)
|
|
695
718
|
"""
|
|
696
719
|
frame_start = time.time()
|
|
697
720
|
|
|
@@ -865,7 +888,8 @@ class AsyncCameraWorker:
|
|
|
865
888
|
height: int,
|
|
866
889
|
frame_counter: int,
|
|
867
890
|
camera_location: str,
|
|
868
|
-
read_time: float
|
|
891
|
+
read_time: float,
|
|
892
|
+
video_ts_ms: Optional[float] = None
|
|
869
893
|
):
|
|
870
894
|
"""SHM_MODE: Write raw frame to SHM, send metadata to Redis.
|
|
871
895
|
|
|
@@ -882,6 +906,7 @@ class AsyncCameraWorker:
|
|
|
882
906
|
frame_counter: Current frame number
|
|
883
907
|
camera_location: Camera location string
|
|
884
908
|
read_time: Time taken to read frame
|
|
909
|
+
video_ts_ms: Video file timestamp in milliseconds (None for live streams)
|
|
885
910
|
"""
|
|
886
911
|
frame_start = time.time()
|
|
887
912
|
|
|
@@ -895,7 +920,11 @@ class AsyncCameraWorker:
|
|
|
895
920
|
if is_similar and reference_frame_idx is not None:
|
|
896
921
|
# Frame is similar - send metadata with reference to previous frame
|
|
897
922
|
# Consumer can skip reading SHM and use previous result
|
|
898
|
-
|
|
923
|
+
# Use video PTS for video files, wall clock for live streams
|
|
924
|
+
if video_ts_ms is not None:
|
|
925
|
+
ts_ns = int(video_ts_ms * 1e6) # Convert ms to ns
|
|
926
|
+
else:
|
|
927
|
+
ts_ns = int(time.time() * 1e9)
|
|
899
928
|
shm_buffer = self._shm_buffers.get(stream_key)
|
|
900
929
|
|
|
901
930
|
await self.redis_client.add_shm_metadata(
|
|
@@ -950,7 +979,11 @@ class AsyncCameraWorker:
|
|
|
950
979
|
self._last_shm_frame_idx[stream_key] = frame_idx
|
|
951
980
|
|
|
952
981
|
# Send metadata-only message to Redis
|
|
953
|
-
|
|
982
|
+
# Use video PTS for video files, wall clock for live streams
|
|
983
|
+
if video_ts_ms is not None:
|
|
984
|
+
ts_ns = int(video_ts_ms * 1e6) # Convert ms to ns
|
|
985
|
+
else:
|
|
986
|
+
ts_ns = int(time.time() * 1e9)
|
|
954
987
|
write_start = time.time()
|
|
955
988
|
|
|
956
989
|
await self.redis_client.add_shm_metadata(
|
|
@@ -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
|
|
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
|
-
|
|
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
|
|
File without changes
|
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/matrice_streaming.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/matrice_streaming.egg-info/not-zip-safe
RENAMED
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/matrice_streaming.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/client/__init__.py
RENAMED
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/client/client.py
RENAMED
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/client/client_utils.py
RENAMED
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/deployment.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{matrice_streaming-0.1.72 → matrice_streaming-0.1.74}/src/matrice_streaming/deployment/todo.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|