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.
- matrice_streaming/__init__.py +44 -32
- matrice_streaming/streaming_gateway/camera_streamer/__init__.py +68 -1
- matrice_streaming/streaming_gateway/camera_streamer/async_camera_worker.py +1388 -0
- matrice_streaming/streaming_gateway/camera_streamer/async_ffmpeg_worker.py +966 -0
- matrice_streaming/streaming_gateway/camera_streamer/camera_streamer.py +188 -24
- matrice_streaming/streaming_gateway/camera_streamer/device_detection.py +507 -0
- matrice_streaming/streaming_gateway/camera_streamer/encoding_pool_manager.py +136 -0
- matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_camera_streamer.py +1048 -0
- matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_config.py +192 -0
- matrice_streaming/streaming_gateway/camera_streamer/ffmpeg_worker_manager.py +470 -0
- matrice_streaming/streaming_gateway/camera_streamer/gstreamer_camera_streamer.py +1368 -0
- matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker.py +1063 -0
- matrice_streaming/streaming_gateway/camera_streamer/gstreamer_worker_manager.py +546 -0
- matrice_streaming/streaming_gateway/camera_streamer/message_builder.py +60 -15
- matrice_streaming/streaming_gateway/camera_streamer/nvdec.py +1330 -0
- matrice_streaming/streaming_gateway/camera_streamer/nvdec_worker_manager.py +412 -0
- matrice_streaming/streaming_gateway/camera_streamer/platform_pipelines.py +680 -0
- matrice_streaming/streaming_gateway/camera_streamer/stream_statistics.py +111 -4
- matrice_streaming/streaming_gateway/camera_streamer/video_capture_manager.py +223 -27
- matrice_streaming/streaming_gateway/camera_streamer/worker_manager.py +694 -0
- matrice_streaming/streaming_gateway/debug/__init__.py +27 -2
- matrice_streaming/streaming_gateway/debug/benchmark.py +727 -0
- matrice_streaming/streaming_gateway/debug/debug_gstreamer_gateway.py +599 -0
- matrice_streaming/streaming_gateway/debug/debug_streaming_gateway.py +245 -95
- matrice_streaming/streaming_gateway/debug/debug_utils.py +29 -0
- matrice_streaming/streaming_gateway/debug/test_videoplayback.py +318 -0
- matrice_streaming/streaming_gateway/dynamic_camera_manager.py +656 -39
- matrice_streaming/streaming_gateway/metrics_reporter.py +676 -139
- matrice_streaming/streaming_gateway/streaming_action.py +71 -20
- matrice_streaming/streaming_gateway/streaming_gateway.py +1026 -78
- matrice_streaming/streaming_gateway/streaming_gateway_utils.py +175 -20
- matrice_streaming/streaming_gateway/streaming_status_listener.py +89 -0
- {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/METADATA +1 -1
- matrice_streaming-0.1.65.dist-info/RECORD +56 -0
- matrice_streaming-0.1.14.dist-info/RECORD +0 -38
- {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/WHEEL +0 -0
- {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/licenses/LICENSE.txt +0 -0
- {matrice_streaming-0.1.14.dist-info → matrice_streaming-0.1.65.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,599 @@
|
|
|
1
|
+
"""Debug GStreamer gateway for testing without Redis/Kafka/API."""
|
|
2
|
+
import logging
|
|
3
|
+
import time
|
|
4
|
+
from typing import List, Optional, Dict, Any
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from .debug_stream_backend import DebugStreamBackend
|
|
8
|
+
from .debug_utils import MockSession, create_debug_input_streams, create_camera_configs_from_streams
|
|
9
|
+
|
|
10
|
+
# Check GStreamer availability
|
|
11
|
+
GSTREAMER_AVAILABLE = False
|
|
12
|
+
GSTREAMER_WORKER_AVAILABLE = False
|
|
13
|
+
try:
|
|
14
|
+
from ..camera_streamer.gstreamer_camera_streamer import (
|
|
15
|
+
GStreamerCameraStreamer,
|
|
16
|
+
GStreamerConfig,
|
|
17
|
+
is_gstreamer_available,
|
|
18
|
+
)
|
|
19
|
+
GSTREAMER_AVAILABLE = is_gstreamer_available()
|
|
20
|
+
|
|
21
|
+
# Try to import GStreamerWorkerManager
|
|
22
|
+
from ..camera_streamer.gstreamer_worker_manager import GStreamerWorkerManager
|
|
23
|
+
GSTREAMER_WORKER_AVAILABLE = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class DebugGStreamerGateway:
|
|
29
|
+
"""Debug version of GStreamer streaming gateway for local testing.
|
|
30
|
+
|
|
31
|
+
This class allows you to test the complete GStreamer pipeline using:
|
|
32
|
+
- Local video files (MP4, AVI, etc.)
|
|
33
|
+
- RTSP streams
|
|
34
|
+
- USB cameras
|
|
35
|
+
- Test patterns
|
|
36
|
+
|
|
37
|
+
Without requiring:
|
|
38
|
+
- Kafka or Redis servers
|
|
39
|
+
- API authentication
|
|
40
|
+
- Network connectivity
|
|
41
|
+
- Real streaming gateway configuration
|
|
42
|
+
|
|
43
|
+
Perfect for:
|
|
44
|
+
- Local development and testing
|
|
45
|
+
- CI/CD pipelines
|
|
46
|
+
- GStreamer encoder testing (NVENC, x264, OpenH264, JPEG)
|
|
47
|
+
- Performance benchmarking
|
|
48
|
+
- Frame optimization testing
|
|
49
|
+
|
|
50
|
+
Example usage:
|
|
51
|
+
# JPEG frame-by-frame with NVENC fallback
|
|
52
|
+
gateway = DebugGStreamerGateway(
|
|
53
|
+
video_paths=["video1.mp4", "video2.mp4"],
|
|
54
|
+
fps=30,
|
|
55
|
+
gstreamer_encoder="jpeg",
|
|
56
|
+
jpeg_quality=85,
|
|
57
|
+
loop_videos=True
|
|
58
|
+
)
|
|
59
|
+
gateway.start_streaming()
|
|
60
|
+
|
|
61
|
+
# Wait and check stats
|
|
62
|
+
time.sleep(30)
|
|
63
|
+
stats = gateway.get_statistics()
|
|
64
|
+
print(f"FPS: {stats['avg_fps']:.1f}")
|
|
65
|
+
print(f"Bandwidth: {stats['bandwidth_mbps']:.2f} Mbps")
|
|
66
|
+
print(f"Cache hits: {stats['cache_efficiency']:.1f}%")
|
|
67
|
+
|
|
68
|
+
# Stop
|
|
69
|
+
gateway.stop_streaming()
|
|
70
|
+
|
|
71
|
+
# Test NVENC hardware encoding
|
|
72
|
+
gateway = DebugGStreamerGateway(
|
|
73
|
+
video_paths=["high_res_video.mp4"],
|
|
74
|
+
fps=60,
|
|
75
|
+
gstreamer_encoder="nvenc",
|
|
76
|
+
gstreamer_codec="h264",
|
|
77
|
+
gstreamer_preset="low-latency",
|
|
78
|
+
gstreamer_gpu_id=0
|
|
79
|
+
)
|
|
80
|
+
gateway.start_streaming()
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
def __init__(
|
|
84
|
+
self,
|
|
85
|
+
video_paths: List[str],
|
|
86
|
+
fps: int = 30,
|
|
87
|
+
loop_videos: bool = True,
|
|
88
|
+
output_dir: Optional[str] = None,
|
|
89
|
+
save_to_files: bool = False,
|
|
90
|
+
log_messages: bool = True,
|
|
91
|
+
save_frame_data: bool = False,
|
|
92
|
+
width: Optional[int] = None,
|
|
93
|
+
height: Optional[int] = None,
|
|
94
|
+
# GStreamer specific options
|
|
95
|
+
gstreamer_encoder: str = "jpeg", # jpeg, nvenc, x264, openh264, auto
|
|
96
|
+
gstreamer_codec: str = "h264", # h264, h265
|
|
97
|
+
gstreamer_preset: str = "low-latency", # NVENC preset
|
|
98
|
+
gstreamer_gpu_id: int = 0, # GPU device ID
|
|
99
|
+
jpeg_quality: int = 85, # JPEG quality (1-100)
|
|
100
|
+
# Frame optimization
|
|
101
|
+
enable_frame_optimizer: bool = True,
|
|
102
|
+
frame_optimizer_scale: float = 0.4,
|
|
103
|
+
frame_optimizer_threshold: float = 0.05,
|
|
104
|
+
# Worker mode parameters (Mode 4)
|
|
105
|
+
use_workers: bool = False,
|
|
106
|
+
num_workers: Optional[int] = None,
|
|
107
|
+
cpu_percentage: float = 0.8,
|
|
108
|
+
max_cameras_per_worker: int = 10,
|
|
109
|
+
):
|
|
110
|
+
"""Initialize debug GStreamer gateway.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
video_paths: List of video file paths to stream
|
|
114
|
+
fps: Frames per second to stream
|
|
115
|
+
loop_videos: Loop videos continuously
|
|
116
|
+
output_dir: Directory to save debug output
|
|
117
|
+
save_to_files: Save streamed messages to files
|
|
118
|
+
log_messages: Log message metadata
|
|
119
|
+
save_frame_data: Include frame data in saved files
|
|
120
|
+
width: Override video width
|
|
121
|
+
height: Override video height
|
|
122
|
+
gstreamer_encoder: GStreamer encoder (jpeg, nvenc, x264, openh264, auto)
|
|
123
|
+
gstreamer_codec: Codec for hardware/software encoders (h264, h265)
|
|
124
|
+
gstreamer_preset: NVENC preset (low-latency, high-quality, lossless)
|
|
125
|
+
gstreamer_gpu_id: GPU device ID for NVENC
|
|
126
|
+
jpeg_quality: JPEG quality 1-100 (higher=better, larger)
|
|
127
|
+
enable_frame_optimizer: Enable frame similarity detection
|
|
128
|
+
frame_optimizer_scale: Downscale factor for similarity detection
|
|
129
|
+
frame_optimizer_threshold: Similarity threshold (lower=stricter)
|
|
130
|
+
use_workers: Use multi-process GStreamerWorkerManager (Mode 4) instead of single-threaded
|
|
131
|
+
num_workers: Number of worker processes (auto-calculated if None)
|
|
132
|
+
cpu_percentage: Percentage of CPU cores to use for auto-calculation
|
|
133
|
+
max_cameras_per_worker: Maximum cameras per worker process
|
|
134
|
+
"""
|
|
135
|
+
# Check GStreamer availability
|
|
136
|
+
if not GSTREAMER_AVAILABLE:
|
|
137
|
+
raise RuntimeError(
|
|
138
|
+
"GStreamer not available. Install with:\n"
|
|
139
|
+
" pip install PyGObject\n"
|
|
140
|
+
" apt-get install gstreamer1.0-tools gstreamer1.0-plugins-*"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Check worker mode availability
|
|
144
|
+
if use_workers and not GSTREAMER_WORKER_AVAILABLE:
|
|
145
|
+
raise RuntimeError(
|
|
146
|
+
"GStreamerWorkerManager not available. Worker mode requires the full package."
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Validate video paths
|
|
150
|
+
for video_path in video_paths:
|
|
151
|
+
if not Path(video_path).exists():
|
|
152
|
+
raise FileNotFoundError(f"Video file not found: {video_path}")
|
|
153
|
+
|
|
154
|
+
self.video_paths = video_paths
|
|
155
|
+
self.fps = fps
|
|
156
|
+
self.loop_videos = loop_videos
|
|
157
|
+
self.use_workers = use_workers
|
|
158
|
+
self.enable_frame_optimizer = enable_frame_optimizer
|
|
159
|
+
|
|
160
|
+
# Create mock session
|
|
161
|
+
self.session = MockSession()
|
|
162
|
+
|
|
163
|
+
# Create debug stream backend (replaces Kafka/Redis)
|
|
164
|
+
self.stream_backend = DebugStreamBackend(
|
|
165
|
+
output_dir=output_dir,
|
|
166
|
+
save_to_files=save_to_files,
|
|
167
|
+
log_messages=log_messages,
|
|
168
|
+
save_frame_data=save_frame_data
|
|
169
|
+
)
|
|
170
|
+
|
|
171
|
+
# Create input streams from video paths
|
|
172
|
+
self.input_streams = create_debug_input_streams(
|
|
173
|
+
video_paths=video_paths,
|
|
174
|
+
fps=fps,
|
|
175
|
+
loop=loop_videos
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# Override dimensions if provided
|
|
179
|
+
if width or height:
|
|
180
|
+
for stream in self.input_streams:
|
|
181
|
+
if width:
|
|
182
|
+
stream.width = width
|
|
183
|
+
if height:
|
|
184
|
+
stream.height = height
|
|
185
|
+
|
|
186
|
+
# Create GStreamer config
|
|
187
|
+
self.gstreamer_config = GStreamerConfig(
|
|
188
|
+
encoder=gstreamer_encoder,
|
|
189
|
+
codec=gstreamer_codec,
|
|
190
|
+
preset=gstreamer_preset,
|
|
191
|
+
gpu_id=gstreamer_gpu_id,
|
|
192
|
+
jpeg_quality=jpeg_quality,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
# Frame optimizer config
|
|
196
|
+
self.frame_optimizer_config = {
|
|
197
|
+
"scale": frame_optimizer_scale,
|
|
198
|
+
"similarity_threshold": frame_optimizer_threshold,
|
|
199
|
+
"diff_threshold": 15,
|
|
200
|
+
"bg_update_interval": 10,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# Initialize camera streamer or worker manager based on mode
|
|
204
|
+
self.camera_streamer = None
|
|
205
|
+
self.worker_manager = None
|
|
206
|
+
|
|
207
|
+
if use_workers:
|
|
208
|
+
# Mode 4: GStreamerWorkerManager (multi-process GStreamer workers)
|
|
209
|
+
# Create camera configs from input streams
|
|
210
|
+
camera_configs = create_camera_configs_from_streams(self.input_streams)
|
|
211
|
+
|
|
212
|
+
# Create stream config for workers
|
|
213
|
+
stream_config = {
|
|
214
|
+
'service_id': 'debug_gstreamer_gateway',
|
|
215
|
+
'server_type': 'debug',
|
|
216
|
+
'gstreamer_encoder': gstreamer_encoder,
|
|
217
|
+
'gstreamer_codec': gstreamer_codec,
|
|
218
|
+
'gstreamer_preset': gstreamer_preset,
|
|
219
|
+
'gstreamer_gpu_id': gstreamer_gpu_id,
|
|
220
|
+
'jpeg_quality': jpeg_quality,
|
|
221
|
+
'frame_optimizer_enabled': enable_frame_optimizer,
|
|
222
|
+
'frame_optimizer_config': self.frame_optimizer_config,
|
|
223
|
+
# Debug backend will be injected into workers
|
|
224
|
+
'debug_mode': True,
|
|
225
|
+
'debug_backend_config': {
|
|
226
|
+
'output_dir': output_dir,
|
|
227
|
+
'save_to_files': save_to_files,
|
|
228
|
+
'log_messages': log_messages,
|
|
229
|
+
'save_frame_data': save_frame_data,
|
|
230
|
+
},
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
self.worker_manager = GStreamerWorkerManager(
|
|
234
|
+
camera_configs=camera_configs,
|
|
235
|
+
stream_config=stream_config,
|
|
236
|
+
num_workers=num_workers,
|
|
237
|
+
cpu_percentage=cpu_percentage,
|
|
238
|
+
max_cameras_per_worker=max_cameras_per_worker,
|
|
239
|
+
gstreamer_encoder=gstreamer_encoder,
|
|
240
|
+
gstreamer_codec=gstreamer_codec,
|
|
241
|
+
gstreamer_preset=gstreamer_preset,
|
|
242
|
+
gpu_id=gstreamer_gpu_id,
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
self.logger = logging.getLogger(__name__)
|
|
246
|
+
self.logger.info(
|
|
247
|
+
f"DebugGStreamerGateway initialized (Worker Mode): "
|
|
248
|
+
f"{len(video_paths)} videos, "
|
|
249
|
+
f"{fps} fps, "
|
|
250
|
+
f"encoder={gstreamer_encoder}, "
|
|
251
|
+
f"codec={gstreamer_codec}, "
|
|
252
|
+
f"workers={self.worker_manager.num_workers}"
|
|
253
|
+
)
|
|
254
|
+
else:
|
|
255
|
+
# Mode 3: GStreamerCameraStreamer (single-threaded)
|
|
256
|
+
self.camera_streamer = GStreamerCameraStreamer(
|
|
257
|
+
session=self.session,
|
|
258
|
+
service_id="debug_gstreamer_gateway",
|
|
259
|
+
server_type="debug",
|
|
260
|
+
gstreamer_config=self.gstreamer_config,
|
|
261
|
+
frame_optimizer_enabled=enable_frame_optimizer,
|
|
262
|
+
frame_optimizer_config=self.frame_optimizer_config,
|
|
263
|
+
gateway_util=None, # No gateway_util in debug mode
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
# Replace MatriceStream with debug backend
|
|
267
|
+
self.camera_streamer.matrice_stream = self.stream_backend
|
|
268
|
+
|
|
269
|
+
self.logger = logging.getLogger(__name__)
|
|
270
|
+
self.logger.info(
|
|
271
|
+
f"DebugGStreamerGateway initialized (Single-threaded Mode): "
|
|
272
|
+
f"{len(video_paths)} videos, "
|
|
273
|
+
f"{fps} fps, "
|
|
274
|
+
f"encoder={gstreamer_encoder}, "
|
|
275
|
+
f"codec={gstreamer_codec}"
|
|
276
|
+
)
|
|
277
|
+
|
|
278
|
+
# State
|
|
279
|
+
self.is_streaming = False
|
|
280
|
+
self.start_time = None
|
|
281
|
+
|
|
282
|
+
def start_streaming(self, block: bool = False) -> bool:
|
|
283
|
+
"""Start streaming all video files.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
block: If True, block until manually stopped
|
|
287
|
+
|
|
288
|
+
Returns:
|
|
289
|
+
True if started successfully
|
|
290
|
+
"""
|
|
291
|
+
if self.is_streaming:
|
|
292
|
+
self.logger.warning("Already streaming")
|
|
293
|
+
return False
|
|
294
|
+
|
|
295
|
+
self.logger.info(f"Starting GStreamer debug streaming with {len(self.input_streams)} videos")
|
|
296
|
+
|
|
297
|
+
try:
|
|
298
|
+
if self.use_workers:
|
|
299
|
+
# Mode 4: Worker manager mode
|
|
300
|
+
self.worker_manager.start()
|
|
301
|
+
self.is_streaming = True
|
|
302
|
+
self.start_time = time.time()
|
|
303
|
+
self.logger.info(f"GStreamer worker manager started with {self.worker_manager.num_workers} workers")
|
|
304
|
+
else:
|
|
305
|
+
# Mode 3: Single-threaded camera streamer mode
|
|
306
|
+
# Register topics and start streams
|
|
307
|
+
for i, input_stream in enumerate(self.input_streams):
|
|
308
|
+
stream_key = input_stream.camera_key
|
|
309
|
+
topic = input_stream.camera_input_topic
|
|
310
|
+
|
|
311
|
+
# Register topic
|
|
312
|
+
self.camera_streamer.register_stream_topic(stream_key, topic)
|
|
313
|
+
self.stream_backend.setup(topic)
|
|
314
|
+
|
|
315
|
+
# Start streaming
|
|
316
|
+
success = self.camera_streamer.start_background_stream(
|
|
317
|
+
input=input_stream.source,
|
|
318
|
+
fps=input_stream.fps,
|
|
319
|
+
stream_key=stream_key,
|
|
320
|
+
stream_group_key=input_stream.camera_group_key,
|
|
321
|
+
quality=input_stream.quality,
|
|
322
|
+
width=input_stream.width,
|
|
323
|
+
height=input_stream.height,
|
|
324
|
+
simulate_video_file_stream=input_stream.simulate_video_file_stream,
|
|
325
|
+
camera_location=input_stream.camera_location,
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
if not success:
|
|
329
|
+
self.logger.error(f"Failed to start stream {i}: {input_stream.source}")
|
|
330
|
+
self.stop_streaming()
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
self.logger.info(f"Started stream {i}: {input_stream.source}")
|
|
334
|
+
|
|
335
|
+
self.is_streaming = True
|
|
336
|
+
self.start_time = time.time()
|
|
337
|
+
self.logger.info("GStreamer debug streaming started successfully")
|
|
338
|
+
|
|
339
|
+
if block:
|
|
340
|
+
self.logger.info("Blocking mode - press Ctrl+C to stop")
|
|
341
|
+
try:
|
|
342
|
+
while self.is_streaming:
|
|
343
|
+
time.sleep(1)
|
|
344
|
+
except KeyboardInterrupt:
|
|
345
|
+
self.logger.info("Interrupted by user")
|
|
346
|
+
self.stop_streaming()
|
|
347
|
+
|
|
348
|
+
return True
|
|
349
|
+
|
|
350
|
+
except Exception as e:
|
|
351
|
+
self.logger.error(f"Failed to start GStreamer debug streaming: {e}", exc_info=True)
|
|
352
|
+
self.stop_streaming()
|
|
353
|
+
return False
|
|
354
|
+
|
|
355
|
+
def stop_streaming(self):
|
|
356
|
+
"""Stop all streaming."""
|
|
357
|
+
if not self.is_streaming:
|
|
358
|
+
self.logger.warning("Not streaming")
|
|
359
|
+
return
|
|
360
|
+
|
|
361
|
+
self.logger.info("Stopping GStreamer debug streaming")
|
|
362
|
+
|
|
363
|
+
try:
|
|
364
|
+
if self.use_workers:
|
|
365
|
+
# Mode 4: Stop worker manager
|
|
366
|
+
self.worker_manager.stop()
|
|
367
|
+
else:
|
|
368
|
+
# Mode 3: Stop camera streamer
|
|
369
|
+
self.camera_streamer.stop_streaming()
|
|
370
|
+
self.stream_backend.close()
|
|
371
|
+
|
|
372
|
+
self.is_streaming = False
|
|
373
|
+
|
|
374
|
+
runtime = time.time() - self.start_time if self.start_time else 0
|
|
375
|
+
self.logger.info(f"GStreamer debug streaming stopped (runtime: {runtime:.1f}s)")
|
|
376
|
+
|
|
377
|
+
except Exception as e:
|
|
378
|
+
self.logger.error(f"Error stopping GStreamer debug streaming: {e}", exc_info=True)
|
|
379
|
+
|
|
380
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
381
|
+
"""Get comprehensive streaming statistics.
|
|
382
|
+
|
|
383
|
+
Returns:
|
|
384
|
+
Dictionary with streaming statistics including:
|
|
385
|
+
- Basic info (video count, fps, encoder, codec)
|
|
386
|
+
- Runtime metrics (duration, uptime)
|
|
387
|
+
- Transmission stats (frames sent, bytes, topics)
|
|
388
|
+
- Performance metrics (avg fps, bandwidth)
|
|
389
|
+
- Frame optimization metrics (cache efficiency, similarity rate)
|
|
390
|
+
- GStreamer pipeline metrics (per-stream stats)
|
|
391
|
+
"""
|
|
392
|
+
stats = {
|
|
393
|
+
"is_streaming": self.is_streaming,
|
|
394
|
+
"video_count": len(self.video_paths),
|
|
395
|
+
"fps": self.fps,
|
|
396
|
+
"encoder": self.gstreamer_config.encoder,
|
|
397
|
+
"codec": self.gstreamer_config.codec,
|
|
398
|
+
"jpeg_quality": self.gstreamer_config.jpeg_quality,
|
|
399
|
+
"mode": "worker" if self.use_workers else "single-threaded",
|
|
400
|
+
"frame_optimizer_enabled": self.enable_frame_optimizer,
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if self.start_time:
|
|
404
|
+
stats["runtime_seconds"] = time.time() - self.start_time
|
|
405
|
+
|
|
406
|
+
if self.use_workers:
|
|
407
|
+
# Mode 4: Get worker manager stats
|
|
408
|
+
try:
|
|
409
|
+
# Get worker statistics
|
|
410
|
+
worker_stats = self.worker_manager.get_worker_statistics()
|
|
411
|
+
stats["worker_stats"] = worker_stats
|
|
412
|
+
|
|
413
|
+
# Calculate aggregate transmission stats from health_reports
|
|
414
|
+
total_frames = 0
|
|
415
|
+
total_bytes = 0
|
|
416
|
+
active_streams = 0
|
|
417
|
+
total_errors = 0
|
|
418
|
+
|
|
419
|
+
# The worker_stats has a 'health_reports' dict with per-worker metrics
|
|
420
|
+
health_reports = worker_stats.get('health_reports', {})
|
|
421
|
+
for worker_id, report in health_reports.items():
|
|
422
|
+
if isinstance(report, dict):
|
|
423
|
+
metrics = report.get('metrics', {})
|
|
424
|
+
total_frames += metrics.get('frames_encoded', 0)
|
|
425
|
+
# Note: bytes aren't tracked directly, estimate from frames
|
|
426
|
+
active_streams += report.get('active_cameras', 0)
|
|
427
|
+
total_errors += metrics.get('encoding_errors', 0)
|
|
428
|
+
|
|
429
|
+
# Estimate bytes (we don't have direct tracking in gstreamer_worker)
|
|
430
|
+
# Using rough estimate of 30KB per JPEG frame at 640x480
|
|
431
|
+
estimated_bytes = total_frames * 30000
|
|
432
|
+
|
|
433
|
+
stats["transmission_stats"] = {
|
|
434
|
+
"total_frames_sent": total_frames,
|
|
435
|
+
"total_bytes_sent": estimated_bytes,
|
|
436
|
+
"active_streams": active_streams,
|
|
437
|
+
"num_workers": worker_stats.get('num_workers', self.worker_manager.num_workers),
|
|
438
|
+
"running_workers": worker_stats.get('running_workers', 0),
|
|
439
|
+
"total_errors": total_errors,
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
# Calculate derived metrics
|
|
443
|
+
if self.start_time:
|
|
444
|
+
duration = time.time() - self.start_time
|
|
445
|
+
stats["avg_fps"] = total_frames / duration if duration > 0 else 0
|
|
446
|
+
stats["bandwidth_mbps"] = (estimated_bytes * 8) / (duration * 1_000_000) if duration > 0 else 0
|
|
447
|
+
|
|
448
|
+
except Exception as e:
|
|
449
|
+
self.logger.warning(f"Failed to get worker stats: {e}")
|
|
450
|
+
stats["transmission_stats"] = {}
|
|
451
|
+
else:
|
|
452
|
+
# Mode 3: Get camera streamer stats
|
|
453
|
+
try:
|
|
454
|
+
transmission_stats = self.camera_streamer.get_transmission_stats()
|
|
455
|
+
|
|
456
|
+
# Normalize field names for consistency
|
|
457
|
+
# GStreamerCameraStreamer uses total_frames_processed
|
|
458
|
+
total_frames = transmission_stats.get("total_frames_processed", 0)
|
|
459
|
+
total_bytes = transmission_stats.get("total_bytes_sent", 0)
|
|
460
|
+
|
|
461
|
+
# Get GStreamer-specific stats
|
|
462
|
+
gstreamer_stats = transmission_stats.get("gstreamer", {})
|
|
463
|
+
if not total_bytes and gstreamer_stats:
|
|
464
|
+
total_bytes = gstreamer_stats.get("total_bytes", 0)
|
|
465
|
+
|
|
466
|
+
# Get backend stats for additional metrics
|
|
467
|
+
backend_stats = self.stream_backend.get_statistics()
|
|
468
|
+
if not total_frames and backend_stats:
|
|
469
|
+
total_frames = backend_stats.get("total_messages", 0)
|
|
470
|
+
|
|
471
|
+
# Update transmission_stats with normalized field names
|
|
472
|
+
transmission_stats["total_frames_sent"] = total_frames
|
|
473
|
+
transmission_stats["total_bytes_sent"] = total_bytes
|
|
474
|
+
stats["transmission_stats"] = transmission_stats
|
|
475
|
+
|
|
476
|
+
# Calculate derived metrics
|
|
477
|
+
if self.start_time:
|
|
478
|
+
duration = time.time() - self.start_time
|
|
479
|
+
stats["avg_fps"] = total_frames / duration if duration > 0 else 0
|
|
480
|
+
stats["bandwidth_mbps"] = (total_bytes * 8) / (duration * 1_000_000) if duration > 0 else 0
|
|
481
|
+
|
|
482
|
+
except Exception as e:
|
|
483
|
+
self.logger.warning(f"Failed to get transmission stats: {e}")
|
|
484
|
+
|
|
485
|
+
# Get frame optimizer metrics (only available in single-threaded mode)
|
|
486
|
+
try:
|
|
487
|
+
if self.camera_streamer.frame_optimizer.enabled:
|
|
488
|
+
opt_metrics = self.camera_streamer.frame_optimizer.get_metrics()
|
|
489
|
+
stats["frame_optimizer_metrics"] = opt_metrics
|
|
490
|
+
|
|
491
|
+
# Calculate cache efficiency
|
|
492
|
+
total_checks = opt_metrics.get("total_checks", 0)
|
|
493
|
+
similar_count = opt_metrics.get("similar_count", 0)
|
|
494
|
+
stats["cache_efficiency"] = (similar_count / total_checks * 100) if total_checks > 0 else 0
|
|
495
|
+
|
|
496
|
+
except Exception as e:
|
|
497
|
+
self.logger.warning(f"Failed to get frame optimizer metrics: {e}")
|
|
498
|
+
|
|
499
|
+
# Get backend stats
|
|
500
|
+
try:
|
|
501
|
+
stats["backend_stats"] = self.stream_backend.get_statistics()
|
|
502
|
+
except Exception as e:
|
|
503
|
+
self.logger.warning(f"Failed to get backend stats: {e}")
|
|
504
|
+
|
|
505
|
+
# Get per-stream timing stats
|
|
506
|
+
try:
|
|
507
|
+
timing_stats = {}
|
|
508
|
+
for input_stream in self.input_streams:
|
|
509
|
+
stream_key = input_stream.camera_key
|
|
510
|
+
timing = self.camera_streamer.get_stream_timing_stats(stream_key)
|
|
511
|
+
if timing:
|
|
512
|
+
timing_stats[stream_key] = timing
|
|
513
|
+
stats["timing_stats"] = timing_stats
|
|
514
|
+
except Exception as e:
|
|
515
|
+
self.logger.warning(f"Failed to get timing stats: {e}")
|
|
516
|
+
|
|
517
|
+
return stats
|
|
518
|
+
|
|
519
|
+
def get_timing_stats(self, stream_key: Optional[str] = None) -> Dict[str, Any]:
|
|
520
|
+
"""Get detailed timing statistics.
|
|
521
|
+
|
|
522
|
+
Args:
|
|
523
|
+
stream_key: Specific stream or None for all
|
|
524
|
+
|
|
525
|
+
Returns:
|
|
526
|
+
Timing statistics with read_time, write_time, process_time breakdown
|
|
527
|
+
"""
|
|
528
|
+
if self.use_workers:
|
|
529
|
+
# Worker mode doesn't support per-stream timing stats directly
|
|
530
|
+
return {"mode": "worker", "note": "Per-stream timing not available in worker mode"}
|
|
531
|
+
return self.camera_streamer.get_stream_timing_stats(stream_key)
|
|
532
|
+
|
|
533
|
+
def produce_request(self, input_data: bytes, topic: str, key: Optional[str] = None) -> bool:
|
|
534
|
+
"""Synchronous message production for testing.
|
|
535
|
+
|
|
536
|
+
Args:
|
|
537
|
+
input_data: Message data
|
|
538
|
+
topic: Topic/stream name
|
|
539
|
+
key: Message key
|
|
540
|
+
|
|
541
|
+
Returns:
|
|
542
|
+
True if successful
|
|
543
|
+
"""
|
|
544
|
+
return self.camera_streamer.produce_request(input_data, topic, key)
|
|
545
|
+
|
|
546
|
+
async def async_produce_request(self, input_data: bytes, topic: str, key: Optional[str] = None) -> bool:
|
|
547
|
+
"""Async message production for testing.
|
|
548
|
+
|
|
549
|
+
Args:
|
|
550
|
+
input_data: Message data
|
|
551
|
+
topic: Topic/stream name
|
|
552
|
+
key: Message key
|
|
553
|
+
|
|
554
|
+
Returns:
|
|
555
|
+
True if successful
|
|
556
|
+
"""
|
|
557
|
+
return await self.camera_streamer.async_produce_request(input_data, topic, key)
|
|
558
|
+
|
|
559
|
+
def refresh_connection_info(self) -> bool:
|
|
560
|
+
"""Refresh connection (no-op for debug mode).
|
|
561
|
+
|
|
562
|
+
Returns:
|
|
563
|
+
True (always successful in debug mode)
|
|
564
|
+
"""
|
|
565
|
+
self.logger.info("refresh_connection_info called (no-op in debug mode)")
|
|
566
|
+
return True
|
|
567
|
+
|
|
568
|
+
def reset_stats(self):
|
|
569
|
+
"""Reset all statistics."""
|
|
570
|
+
if self.use_workers:
|
|
571
|
+
# Worker mode - no direct reset capability
|
|
572
|
+
self.logger.info("Statistics reset (worker mode - limited reset)")
|
|
573
|
+
else:
|
|
574
|
+
self.camera_streamer.reset_transmission_stats()
|
|
575
|
+
if self.camera_streamer.frame_optimizer.enabled:
|
|
576
|
+
self.camera_streamer.frame_optimizer.reset_metrics()
|
|
577
|
+
self.logger.info("Statistics reset")
|
|
578
|
+
|
|
579
|
+
def __enter__(self):
|
|
580
|
+
"""Context manager entry."""
|
|
581
|
+
return self
|
|
582
|
+
|
|
583
|
+
def __exit__(self, exc_type, exc_val, exc_tb):
|
|
584
|
+
"""Context manager exit."""
|
|
585
|
+
if exc_type:
|
|
586
|
+
self.logger.error(f"Exception in context: {exc_val}", exc_info=True)
|
|
587
|
+
self.stop_streaming()
|
|
588
|
+
|
|
589
|
+
def __repr__(self):
|
|
590
|
+
"""String representation."""
|
|
591
|
+
mode_str = f"workers={self.worker_manager.num_workers}" if self.use_workers else "single-threaded"
|
|
592
|
+
return (
|
|
593
|
+
f"DebugGStreamerGateway("
|
|
594
|
+
f"videos={len(self.video_paths)}, "
|
|
595
|
+
f"fps={self.fps}, "
|
|
596
|
+
f"encoder={self.gstreamer_config.encoder}, "
|
|
597
|
+
f"mode={mode_str}, "
|
|
598
|
+
f"streaming={self.is_streaming})"
|
|
599
|
+
)
|