matrice-streaming 0.1.14__py3-none-any.whl → 0.1.65__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.
Files changed (38) hide show
  1. matrice_streaming/__init__.py +44 -32
  2. matrice_streaming/streaming_gateway/camera_streamer/__init__.py +68 -1
  3. matrice_streaming/streaming_gateway/camera_streamer/async_camera_worker.py +1388 -0
  4. matrice_streaming/streaming_gateway/camera_streamer/async_ffmpeg_worker.py +966 -0
  5. matrice_streaming/streaming_gateway/camera_streamer/camera_streamer.py +188 -24
  6. matrice_streaming/streaming_gateway/camera_streamer/device_detection.py +507 -0
  7. matrice_streaming/streaming_gateway/camera_streamer/encoding_pool_manager.py +136 -0
  8. matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_camera_streamer.py +1048 -0
  9. matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_config.py +192 -0
  10. matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_worker_manager.py +470 -0
  11. matrice_streaming/streaming_gateway/camera_streamer/gstreamer_camera_streamer.py +1368 -0
  12. matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker.py +1063 -0
  13. matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker_manager.py +546 -0
  14. matrice_streaming/streaming_gateway/camera_streamer/message_builder.py +60 -15
  15. matrice_streaming/streaming_gateway/camera_streamer/nvdec.py +1330 -0
  16. matrice_streaming/streaming_gateway/camera_streamer/nvdec_worker_manager.py +412 -0
  17. matrice_streaming/streaming_gateway/camera_streamer/platform_pipelines.py +680 -0
  18. matrice_streaming/streaming_gateway/camera_streamer/stream_statistics.py +111 -4
  19. matrice_streaming/streaming_gateway/camera_streamer/video_capture_manager.py +223 -27
  20. matrice_streaming/streaming_gateway/camera_streamer/worker_manager.py +694 -0
  21. matrice_streaming/streaming_gateway/debug/__init__.py +27 -2
  22. matrice_streaming/streaming_gateway/debug/benchmark.py +727 -0
  23. matrice_streaming/streaming_gateway/debug/debug_gstreamer_gateway.py +599 -0
  24. matrice_streaming/streaming_gateway/debug/debug_streaming_gateway.py +245 -95
  25. matrice_streaming/streaming_gateway/debug/debug_utils.py +29 -0
  26. matrice_streaming/streaming_gateway/debug/test_videoplayback.py +318 -0
  27. matrice_streaming/streaming_gateway/dynamic_camera_manager.py +656 -39
  28. matrice_streaming/streaming_gateway/metrics_reporter.py +676 -139
  29. matrice_streaming/streaming_gateway/streaming_action.py +71 -20
  30. matrice_streaming/streaming_gateway/streaming_gateway.py +1026 -78
  31. matrice_streaming/streaming_gateway/streaming_gateway_utils.py +175 -20
  32. matrice_streaming/streaming_gateway/streaming_status_listener.py +89 -0
  33. {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/METADATA +1 -1
  34. matrice_streaming-0.1.65.dist-info/RECORD +56 -0
  35. matrice_streaming-0.1.14.dist-info/RECORD +0 -38
  36. {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/WHEEL +0 -0
  37. {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/licenses/LICENSE.txt +0 -0
  38. {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/top_level.txt +0 -0
@@ -5,8 +5,9 @@ from typing import List, Optional, Dict, Any
5
5
  from pathlib import Path
6
6
 
7
7
  from .debug_stream_backend import DebugStreamBackend
8
- from .debug_utils import MockSession, create_debug_input_streams
8
+ from .debug_utils import MockSession, create_debug_input_streams, create_camera_configs_from_streams
9
9
  from ..camera_streamer.camera_streamer import CameraStreamer
10
+ from ..camera_streamer.worker_manager import WorkerManager
10
11
 
11
12
  class DebugStreamingGateway:
12
13
  """Debug version of StreamingGateway that works without external dependencies.
@@ -55,9 +56,14 @@ class DebugStreamingGateway:
55
56
  save_frame_data: bool = False,
56
57
  width: Optional[int] = None,
57
58
  height: Optional[int] = None,
59
+ # Worker mode parameters (Mode 2)
60
+ use_workers: bool = False,
61
+ num_workers: Optional[int] = None,
62
+ cpu_percentage: float = 0.8,
63
+ max_cameras_per_worker: int = 10,
58
64
  ):
59
65
  """Initialize debug streaming gateway.
60
-
66
+
61
67
  Args:
62
68
  video_paths: List of video file paths to stream
63
69
  fps: Frames per second to stream
@@ -71,20 +77,25 @@ class DebugStreamingGateway:
71
77
  save_frame_data: Include frame data in saved files
72
78
  width: Override video width
73
79
  height: Override video height
80
+ use_workers: Use multi-process WorkerManager (Mode 2) instead of single-threaded CameraStreamer
81
+ num_workers: Number of worker processes (auto-calculated if None)
82
+ cpu_percentage: Percentage of CPU cores to use for auto-calculation
83
+ max_cameras_per_worker: Maximum cameras per worker process
74
84
  """
75
85
  # Validate video paths
76
86
  for video_path in video_paths:
77
87
  if not Path(video_path).exists():
78
88
  raise FileNotFoundError(f"Video file not found: {video_path}")
79
-
89
+
80
90
  self.video_paths = video_paths
81
91
  self.fps = fps
82
92
  self.video_codec = video_codec
83
93
  self.loop_videos = loop_videos
84
-
94
+ self.use_workers = use_workers
95
+
85
96
  # Create mock session
86
97
  self.session = MockSession()
87
-
98
+
88
99
  # Create debug stream backend (replaces Kafka/Redis)
89
100
  self.stream_backend = DebugStreamBackend(
90
101
  output_dir=output_dir,
@@ -92,14 +103,14 @@ class DebugStreamingGateway:
92
103
  log_messages=log_messages,
93
104
  save_frame_data=save_frame_data
94
105
  )
95
-
106
+
96
107
  # Create input streams from video paths
97
108
  self.input_streams = create_debug_input_streams(
98
109
  video_paths=video_paths,
99
110
  fps=fps,
100
111
  loop=loop_videos
101
112
  )
102
-
113
+
103
114
  # Override dimensions if provided
104
115
  if width or height:
105
116
  for stream in self.input_streams:
@@ -107,82 +118,141 @@ class DebugStreamingGateway:
107
118
  stream.width = width
108
119
  if height:
109
120
  stream.height = height
110
-
111
- # Create camera streamer with debug backend
112
- self.camera_streamer = CameraStreamer(
113
- session=self.session,
114
- service_id="debug_streaming_gateway",
115
- server_type="debug",
116
- stream_config={},
117
- video_codec=video_codec,
118
- h265_quality=h265_quality,
119
- use_hardware=use_hardware,
120
- )
121
-
122
- # Replace MatriceStream with debug backend
123
- self.camera_streamer.matrice_stream = self.stream_backend
124
-
121
+
122
+ # Determine h265_mode from video_codec
123
+ # h265_mode can be "frame" or "stream" (chunk is part of stream mode)
124
+ h265_mode = "frame"
125
+ if "chunk" in video_codec.lower() or "stream" in video_codec.lower():
126
+ h265_mode = "stream"
127
+
128
+ # Initialize camera streamer or worker manager based on mode
129
+ self.camera_streamer = None
130
+ self.worker_manager = None
131
+
132
+ if use_workers:
133
+ # Mode 2: WorkerManager (multi-process async workers)
134
+ # Create camera configs from input streams
135
+ camera_configs = create_camera_configs_from_streams(self.input_streams)
136
+
137
+ # Create stream config for workers
138
+ stream_config = {
139
+ 'service_id': 'debug_streaming_gateway',
140
+ 'server_type': 'debug',
141
+ 'video_codec': video_codec,
142
+ 'h265_quality': h265_quality,
143
+ 'use_hardware': use_hardware,
144
+ 'h265_mode': h265_mode,
145
+ # Debug backend will be injected into workers
146
+ 'debug_mode': True,
147
+ 'debug_backend_config': {
148
+ 'output_dir': output_dir,
149
+ 'save_to_files': save_to_files,
150
+ 'log_messages': log_messages,
151
+ 'save_frame_data': save_frame_data,
152
+ },
153
+ }
154
+
155
+ self.worker_manager = WorkerManager(
156
+ camera_configs=camera_configs,
157
+ stream_config=stream_config,
158
+ num_workers=num_workers,
159
+ cpu_percentage=cpu_percentage,
160
+ max_cameras_per_worker=max_cameras_per_worker,
161
+ )
162
+
163
+ self.logger = logging.getLogger(__name__)
164
+ self.logger.info(
165
+ f"DebugStreamingGateway initialized (Worker Mode): "
166
+ f"{len(video_paths)} videos, "
167
+ f"{fps} fps, "
168
+ f"codec={video_codec}, "
169
+ f"workers={self.worker_manager.num_workers}"
170
+ )
171
+ else:
172
+ # Mode 1: CameraStreamer (single-threaded)
173
+ self.camera_streamer = CameraStreamer(
174
+ session=self.session,
175
+ service_id="debug_streaming_gateway",
176
+ server_type="debug",
177
+ video_codec=video_codec,
178
+ h265_quality=h265_quality,
179
+ use_hardware=use_hardware,
180
+ h265_mode=h265_mode,
181
+ gateway_util=None, # No gateway_util in debug mode
182
+ )
183
+
184
+ # Replace MatriceStream with debug backend
185
+ self.camera_streamer.matrice_stream = self.stream_backend
186
+
187
+ self.logger = logging.getLogger(__name__)
188
+ self.logger.info(
189
+ f"DebugStreamingGateway initialized (Single-threaded Mode): "
190
+ f"{len(video_paths)} videos, "
191
+ f"{fps} fps, "
192
+ f"codec={video_codec}"
193
+ )
194
+
125
195
  # State
126
196
  self.is_streaming = False
127
197
  self.start_time = None
128
-
129
- self.logger = logging.getLogger(__name__)
130
- self.logger.info(
131
- f"DebugStreamingGateway initialized: "
132
- f"{len(video_paths)} videos, "
133
- f"{fps} fps, "
134
- f"codec={video_codec}"
135
- )
136
198
 
137
199
  def start_streaming(self, block: bool = False) -> bool:
138
200
  """Start streaming all video files.
139
-
201
+
140
202
  Args:
141
203
  block: If True, block until manually stopped
142
-
204
+
143
205
  Returns:
144
206
  True if started successfully
145
207
  """
146
208
  if self.is_streaming:
147
209
  self.logger.warning("Already streaming")
148
210
  return False
149
-
211
+
150
212
  self.logger.info(f"Starting debug streaming with {len(self.input_streams)} videos")
151
-
213
+
152
214
  try:
153
- # Register topics and start streams
154
- for i, input_stream in enumerate(self.input_streams):
155
- stream_key = input_stream.camera_key
156
- topic = input_stream.camera_input_topic
157
-
158
- # Register topic
159
- self.camera_streamer.register_stream_topic(stream_key, topic)
160
- self.stream_backend.setup(topic)
161
-
162
- # Start streaming
163
- success = self.camera_streamer.start_background_stream(
164
- input=input_stream.source,
165
- fps=input_stream.fps,
166
- stream_key=stream_key,
167
- stream_group_key=input_stream.camera_group_key,
168
- quality=input_stream.quality,
169
- width=input_stream.width,
170
- height=input_stream.height,
171
- simulate_video_file_stream=input_stream.simulate_video_file_stream,
172
- camera_location=input_stream.camera_location,
173
- )
174
-
175
- if not success:
176
- self.logger.error(f"Failed to start stream {i}: {input_stream.source}")
177
- self.stop_streaming()
178
- return False
179
-
180
- self.logger.info(f"Started stream {i}: {input_stream.source}")
181
-
182
- self.is_streaming = True
183
- self.start_time = time.time()
184
- self.logger.info("Debug streaming started successfully")
185
-
215
+ if self.use_workers:
216
+ # Mode 2: Worker manager mode
217
+ self.worker_manager.start()
218
+ self.is_streaming = True
219
+ self.start_time = time.time()
220
+ self.logger.info(f"Worker manager started with {self.worker_manager.num_workers} workers")
221
+ else:
222
+ # Mode 1: Single-threaded camera streamer mode
223
+ # Register topics and start streams
224
+ for i, input_stream in enumerate(self.input_streams):
225
+ stream_key = input_stream.camera_key
226
+ topic = input_stream.camera_input_topic
227
+
228
+ # Register topic
229
+ self.camera_streamer.register_stream_topic(stream_key, topic)
230
+ self.stream_backend.setup(topic)
231
+
232
+ # Start streaming
233
+ success = self.camera_streamer.start_background_stream(
234
+ input=input_stream.source,
235
+ fps=input_stream.fps,
236
+ stream_key=stream_key,
237
+ stream_group_key=input_stream.camera_group_key,
238
+ quality=input_stream.quality,
239
+ width=input_stream.width,
240
+ height=input_stream.height,
241
+ simulate_video_file_stream=input_stream.simulate_video_file_stream,
242
+ camera_location=input_stream.camera_location,
243
+ )
244
+
245
+ if not success:
246
+ self.logger.error(f"Failed to start stream {i}: {input_stream.source}")
247
+ self.stop_streaming()
248
+ return False
249
+
250
+ self.logger.info(f"Started stream {i}: {input_stream.source}")
251
+
252
+ self.is_streaming = True
253
+ self.start_time = time.time()
254
+ self.logger.info("Debug streaming started successfully")
255
+
186
256
  if block:
187
257
  self.logger.info("Blocking mode - press Ctrl+C to stop")
188
258
  try:
@@ -191,9 +261,9 @@ class DebugStreamingGateway:
191
261
  except KeyboardInterrupt:
192
262
  self.logger.info("Interrupted by user")
193
263
  self.stop_streaming()
194
-
264
+
195
265
  return True
196
-
266
+
197
267
  except Exception as e:
198
268
  self.logger.error(f"Failed to start debug streaming: {e}", exc_info=True)
199
269
  self.stop_streaming()
@@ -204,23 +274,29 @@ class DebugStreamingGateway:
204
274
  if not self.is_streaming:
205
275
  self.logger.warning("Not streaming")
206
276
  return
207
-
277
+
208
278
  self.logger.info("Stopping debug streaming")
209
-
279
+
210
280
  try:
211
- self.camera_streamer.stop_streaming()
212
- self.stream_backend.close()
281
+ if self.use_workers:
282
+ # Mode 2: Stop worker manager
283
+ self.worker_manager.stop()
284
+ else:
285
+ # Mode 1: Stop camera streamer
286
+ self.camera_streamer.stop_streaming()
287
+ self.stream_backend.close()
288
+
213
289
  self.is_streaming = False
214
-
290
+
215
291
  runtime = time.time() - self.start_time if self.start_time else 0
216
292
  self.logger.info(f"Debug streaming stopped (runtime: {runtime:.1f}s)")
217
-
293
+
218
294
  except Exception as e:
219
295
  self.logger.error(f"Error stopping debug streaming: {e}", exc_info=True)
220
296
 
221
297
  def get_statistics(self) -> Dict[str, Any]:
222
298
  """Get streaming statistics.
223
-
299
+
224
300
  Returns:
225
301
  Dictionary with streaming statistics
226
302
  """
@@ -229,40 +305,112 @@ class DebugStreamingGateway:
229
305
  "video_count": len(self.video_paths),
230
306
  "fps": self.fps,
231
307
  "video_codec": self.video_codec,
308
+ "mode": "worker" if self.use_workers else "single-threaded",
232
309
  }
233
-
310
+
234
311
  if self.start_time:
235
312
  stats["runtime_seconds"] = time.time() - self.start_time
236
-
237
- # Get camera streamer stats
238
- try:
239
- stats["transmission_stats"] = self.camera_streamer.get_transmission_stats()
240
- except Exception as e:
241
- self.logger.warning(f"Failed to get transmission stats: {e}")
242
-
243
- # Get backend stats
244
- try:
245
- stats["backend_stats"] = self.stream_backend.get_statistics()
246
- except Exception as e:
247
- self.logger.warning(f"Failed to get backend stats: {e}")
248
-
313
+
314
+ if self.use_workers:
315
+ # Mode 2: Get worker manager stats
316
+ try:
317
+ # Get worker statistics
318
+ worker_stats = self.worker_manager.get_worker_statistics()
319
+ stats["worker_stats"] = worker_stats
320
+
321
+ # Calculate aggregate transmission stats from health_reports
322
+ total_frames = 0
323
+ total_bytes = 0
324
+ active_streams = 0
325
+ total_errors = 0
326
+
327
+ # The worker_stats has a 'health_reports' dict with per-worker metrics
328
+ health_reports = worker_stats.get('health_reports', {})
329
+ for worker_id, report in health_reports.items():
330
+ if isinstance(report, dict):
331
+ metrics = report.get('metrics', {})
332
+ # Workers use frames_encoded for frame count
333
+ total_frames += metrics.get('frames_encoded', 0)
334
+ total_bytes += metrics.get('bytes_sent', 0)
335
+ active_streams += report.get('active_cameras', 0)
336
+ total_errors += metrics.get('encoding_errors', 0)
337
+
338
+ stats["transmission_stats"] = {
339
+ "total_frames_sent": total_frames,
340
+ "total_bytes_sent": total_bytes,
341
+ "active_streams": active_streams,
342
+ "num_workers": worker_stats.get('num_workers', self.worker_manager.num_workers),
343
+ "running_workers": worker_stats.get('running_workers', 0),
344
+ "total_errors": total_errors,
345
+ }
346
+
347
+ # Calculate derived metrics
348
+ if self.start_time:
349
+ duration = time.time() - self.start_time
350
+ stats["avg_fps"] = total_frames / duration if duration > 0 else 0
351
+ stats["bandwidth_mbps"] = (total_bytes * 8) / (duration * 1_000_000) if duration > 0 else 0
352
+
353
+ except Exception as e:
354
+ self.logger.warning(f"Failed to get worker stats: {e}")
355
+ stats["transmission_stats"] = {}
356
+ else:
357
+ # Mode 1: Get camera streamer stats
358
+ try:
359
+ transmission_stats = self.camera_streamer.get_transmission_stats()
360
+
361
+ # Normalize field names for consistency
362
+ # CameraStreamer uses frames_sent_full, total_frames_processed
363
+ total_frames = transmission_stats.get("total_frames_processed", 0)
364
+ total_bytes = transmission_stats.get("total_bytes_sent", 0)
365
+
366
+ # Get backend stats for additional metrics
367
+ backend_stats = self.stream_backend.get_statistics()
368
+ if not total_frames and backend_stats:
369
+ total_frames = backend_stats.get("total_messages", 0)
370
+
371
+ # Update transmission_stats with normalized field names
372
+ transmission_stats["total_frames_sent"] = total_frames
373
+ stats["transmission_stats"] = transmission_stats
374
+
375
+ # Calculate derived metrics
376
+ if self.start_time:
377
+ duration = time.time() - self.start_time
378
+ stats["avg_fps"] = total_frames / duration if duration > 0 else 0
379
+ stats["bandwidth_mbps"] = (total_bytes * 8) / (duration * 1_000_000) if duration > 0 else 0
380
+
381
+ except Exception as e:
382
+ self.logger.warning(f"Failed to get transmission stats: {e}")
383
+
384
+ # Get backend stats
385
+ try:
386
+ stats["backend_stats"] = self.stream_backend.get_statistics()
387
+ except Exception as e:
388
+ self.logger.warning(f"Failed to get backend stats: {e}")
389
+
249
390
  return stats
250
391
 
251
392
  def get_timing_stats(self, stream_key: Optional[str] = None) -> Dict[str, Any]:
252
393
  """Get timing statistics.
253
-
394
+
254
395
  Args:
255
396
  stream_key: Specific stream or None for all
256
-
397
+
257
398
  Returns:
258
399
  Timing statistics
259
400
  """
401
+ if self.use_workers:
402
+ # Worker mode doesn't support per-stream timing stats directly
403
+ return {"mode": "worker", "note": "Per-stream timing not available in worker mode"}
260
404
  return self.camera_streamer.get_stream_timing_stats(stream_key)
261
-
405
+
262
406
  def reset_stats(self):
263
407
  """Reset all statistics."""
264
- self.camera_streamer.reset_transmission_stats()
265
- self.logger.info("Statistics reset")
408
+ if self.use_workers:
409
+ # Worker mode - no direct reset capability
410
+ self.logger.info("Statistics reset (worker mode - limited reset)")
411
+ else:
412
+ self.camera_streamer.reset_transmission_stats()
413
+ self.logger.info("Statistics reset")
266
414
 
267
415
  def __enter__(self):
268
416
  """Context manager entry."""
@@ -276,10 +424,12 @@ class DebugStreamingGateway:
276
424
 
277
425
  def __repr__(self):
278
426
  """String representation."""
427
+ mode_str = f"workers={self.worker_manager.num_workers}" if self.use_workers else "single-threaded"
279
428
  return (
280
429
  f"DebugStreamingGateway("
281
430
  f"videos={len(self.video_paths)}, "
282
431
  f"fps={self.fps}, "
432
+ f"mode={mode_str}, "
283
433
  f"streaming={self.is_streaming})"
284
434
  )
285
435
 
@@ -278,3 +278,32 @@ def create_debug_input_streams(video_paths: List[str], fps: int = 10, loop: bool
278
278
 
279
279
  return input_streams
280
280
 
281
+
282
+ def create_camera_configs_from_streams(input_streams: List) -> List[Dict[str, Any]]:
283
+ """Convert InputStream objects to camera configs for WorkerManager.
284
+
285
+ Args:
286
+ input_streams: List of InputStream objects
287
+
288
+ Returns:
289
+ List of camera configuration dictionaries for WorkerManager
290
+ """
291
+ camera_configs = []
292
+
293
+ for stream in input_streams:
294
+ config = {
295
+ 'source': stream.source,
296
+ 'stream_key': stream.camera_key,
297
+ 'stream_group_key': stream.camera_group_key,
298
+ 'topic': stream.camera_input_topic,
299
+ 'fps': stream.fps,
300
+ 'quality': stream.quality,
301
+ 'width': stream.width,
302
+ 'height': stream.height,
303
+ 'simulate_video_file_stream': stream.simulate_video_file_stream,
304
+ 'camera_location': stream.camera_location,
305
+ }
306
+ camera_configs.append(config)
307
+
308
+ return camera_configs
309
+