matrice-streaming 0.1.71__py3-none-any.whl → 0.1.73__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.
@@ -101,7 +101,7 @@ class AsyncCameraWorker:
101
101
  # ================================================================
102
102
  drop_stale_frames: bool = False, # Disabled for ML quality
103
103
  use_simple_read: bool = True, # Use cap.read() directly instead of grab/retrieve
104
- pin_cpu_affinity: bool = True, # Pin worker to specific CPU cores
104
+ pin_cpu_affinity: bool = False, # Let OS distribute threads (avoids cache contention)
105
105
  total_workers: int = 1, # Total worker count for CPU affinity calculation
106
106
  buffer_size: int = 1, # Minimal buffer for low latency (cv2_bench uses 1)
107
107
  ):
@@ -240,8 +240,8 @@ class AsyncCameraWorker:
240
240
  # - But TOO many threads causes burst frame arrivals → Redis write queue backup
241
241
  # - Cap at 64 threads to balance I/O parallelism vs write contention
242
242
  num_cameras = len(camera_configs)
243
- # Use 1 thread per camera, capped at 64 to prevent write burst contention
244
- num_capture_threads = min(64, max(8, num_cameras))
243
+ # Use 1 thread per camera, max 64 for reduced contention
244
+ num_capture_threads = min(64, num_cameras)
245
245
  self.capture_executor = ThreadPoolExecutor(max_workers=num_capture_threads)
246
246
  self.num_capture_threads = num_capture_threads
247
247
 
@@ -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
- # Maintain target FPS for ALL sources (video files AND live cameras)
596
- # This prevents overwhelming the encoder by reading at native camera rate (30+ FPS)
597
- frame_interval = 1.0 / fps
598
- frame_elapsed = time.time() - read_start
599
- sleep_time = max(0, frame_interval - frame_elapsed)
600
- if sleep_time > 0:
601
- await asyncio.sleep(sleep_time)
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
- ts_ns = int(time.time() * 1e9)
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
- ts_ns = int(time.time() * 1e9)
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(
@@ -1335,7 +1368,7 @@ def run_async_worker(
1335
1368
  # ================================================================
1336
1369
  drop_stale_frames: bool = False, # Disabled for ML quality
1337
1370
  use_simple_read: bool = True, # Use cap.read() directly instead of grab/retrieve
1338
- pin_cpu_affinity: bool = True,
1371
+ pin_cpu_affinity: bool = False, # Let OS distribute (avoids cache contention)
1339
1372
  total_workers: int = 1,
1340
1373
  buffer_size: int = 1,
1341
1374
  # ================================================================
@@ -45,7 +45,7 @@ class WorkerManager:
45
45
  # ================================================================
46
46
  drop_stale_frames: bool = False, # Use grab()/grab()/retrieve() for latest frame (disabled for ML quality)
47
47
  use_simple_read: bool = True, # Use cap.read() directly instead of grab/retrieve
48
- pin_cpu_affinity: bool = True, # Pin workers to specific CPU cores
48
+ pin_cpu_affinity: bool = False, # Let OS distribute (avoids cache contention)
49
49
  buffer_size: int = 1, # VideoCapture buffer size (1 = minimal latency)
50
50
  # ================================================================
51
51
  # FRAME OPTIMIZER: Control frame similarity detection
@@ -78,17 +78,18 @@ class WorkerManager:
78
78
  cpu_count = os.cpu_count() or 4 # Fallback to 4 if can't detect
79
79
  num_cameras = len(camera_configs)
80
80
 
81
- # For systems with 16+ cores OR large camera counts, use camera-based calculation
82
- # This applies to Docker containers with limited CPU allocation (e.g., 20 cores)
83
- # Too many workers = process overhead; too few = underutilization
84
- # Target: ~25 cameras per worker for better read parallelism with video files
85
- if cpu_count >= 16 or num_cameras >= 100:
81
+ # Small deployments: 1 worker per camera for maximum parallelism
82
+ # Each worker process has its own GIL, enabling true CPU parallelism
83
+ if num_cameras <= 10:
84
+ calculated_workers = max(1, num_cameras)
85
+ # Large deployments: use camera-based calculation
86
+ elif cpu_count >= 16 or num_cameras >= 100:
86
87
  # Use camera-based calculation for better distribution
87
88
  # 1000 cameras / 25 cameras per worker = 40 workers
88
89
  target_cameras_per_worker = 25
89
90
  calculated_workers = max(4, min(num_cameras // target_cameras_per_worker, 50))
90
91
  else:
91
- # Standard calculation for smaller systems
92
+ # Standard calculation for medium systems
92
93
  calculated_workers = max(4, int(cpu_count * cpu_percentage))
93
94
 
94
95
  # Cap at camera count (no point having more workers than cameras)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: matrice_streaming
3
- Version: 0.1.71
3
+ Version: 0.1.73
4
4
  Summary: Common server utilities for Matrice.ai services
5
5
  Author-email: "Matrice.ai" <dipendra@matrice.ai>
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ matrice_streaming/streaming_gateway/streaming_gateway_utils.py,sha256=6kNYgl3f7H
19
19
  matrice_streaming/streaming_gateway/streaming_status_listener.py,sha256=RgbW0xYbbpmO6ZjkVlh6fg8iqkpRaIShR2dQ3SMVFUw,3161
20
20
  matrice_streaming/streaming_gateway/camera_streamer/ARCHITECTURE.md,sha256=kngsqiS1PdNYhihBoMtoiIf3THJ4OM33E_hxExqzKqY,9980
21
21
  matrice_streaming/streaming_gateway/camera_streamer/__init__.py,sha256=nwVY-ySnKvOedeDXakyR_6KPHSz3yzSeaO_4IFMMP4I,2219
22
- matrice_streaming/streaming_gateway/camera_streamer/async_camera_worker.py,sha256=j1ZJYwE4Ohh0DdCGlGWA2c-baMHHZ-e4yX3g1bx-Zig,61199
22
+ matrice_streaming/streaming_gateway/camera_streamer/async_camera_worker.py,sha256=AJOqvG8ZN9JMsImNuP1Pz1CEb0xGsJCPnnU-e8TA8eU,63263
23
23
  matrice_streaming/streaming_gateway/camera_streamer/async_ffmpeg_worker.py,sha256=cD3XocWqamkBE9TlkG757OK6tl_Op45r-cMd-ZgJXaA,37063
24
24
  matrice_streaming/streaming_gateway/camera_streamer/camera_streamer.py,sha256=zv6tWUKQf2uXAxqtSlfwAVDb9N483VCRApPogUWJ9e8,39113
25
25
  matrice_streaming/streaming_gateway/camera_streamer/device_detection.py,sha256=9F4rsbMpIexOIlX8aCj7Q6PFG01kOS1wtgAIQBG0FaM,18463
@@ -39,7 +39,7 @@ matrice_streaming/streaming_gateway/camera_streamer/platform_pipelines.py,sha256
39
39
  matrice_streaming/streaming_gateway/camera_streamer/retry_manager.py,sha256=d8tlGoWoeSlgpCgXbUHTM61ekCQZki7TO1HzL2yPVzk,3607
40
40
  matrice_streaming/streaming_gateway/camera_streamer/stream_statistics.py,sha256=VC1S6ogiHUTlzTqn2wGOC1ClOEjvDgWm9Ydi9sWj-Do,18951
41
41
  matrice_streaming/streaming_gateway/camera_streamer/video_capture_manager.py,sha256=ySnbihdkpFPcBdFK_OFcHE-MgjG2YirUUYgEJHhvTxo,16899
42
- matrice_streaming/streaming_gateway/camera_streamer/worker_manager.py,sha256=y8Ew3TnNHkTMcaAPDy3OsVqx0w2QShpC7cyomvhRyZ8,29355
42
+ matrice_streaming/streaming_gateway/camera_streamer/worker_manager.py,sha256=Vfdz9DaLkkTlRqg2_DKk4pkUmxQbEaI44c82X7ro8OU,29322
43
43
  matrice_streaming/streaming_gateway/debug/README.md,sha256=6GeHClMjJbmVuSKbJ8yOIDqpeAPnLNrHXMFtmubZz0E,11343
44
44
  matrice_streaming/streaming_gateway/debug/__init__.py,sha256=_RtvH0Y12HFiDoKK8BEsMwy6k_iAFjmYYNzMawbOfeQ,2026
45
45
  matrice_streaming/streaming_gateway/debug/benchmark.py,sha256=65la9K2mjiiuADCBSnYpPr9uYFnM89clgw-KHkjSkAU,25563
@@ -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.71.dist-info/licenses/LICENSE.txt,sha256=_uQUZpgO0mRYL5-fPoEvLSbNnLPv6OmbeEDCHXhK6Qc,1066
53
- matrice_streaming-0.1.71.dist-info/METADATA,sha256=rKRMjhTa3a-56AdVkQlK8c925MRGwBU4qw1auHmDAaU,2477
54
- matrice_streaming-0.1.71.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
- matrice_streaming-0.1.71.dist-info/top_level.txt,sha256=PM_trIe8f4JLc90J871rNMYGVM3Po9Inx4As5LrCFUU,18
56
- matrice_streaming-0.1.71.dist-info/RECORD,,
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,,