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
|
@@ -1,13 +1,58 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
import
|
|
2
|
+
import os
|
|
3
3
|
import time
|
|
4
4
|
import threading
|
|
5
5
|
import atexit
|
|
6
|
-
from typing import Dict, List, Optional
|
|
6
|
+
from typing import Dict, List, Optional, Any
|
|
7
7
|
from .camera_streamer import CameraStreamer
|
|
8
|
-
from .
|
|
8
|
+
from .camera_streamer.worker_manager import WorkerManager
|
|
9
|
+
from .streaming_gateway_utils import (
|
|
10
|
+
StreamingGatewayUtil,
|
|
11
|
+
InputStream,
|
|
12
|
+
input_stream_to_camera_config,
|
|
13
|
+
build_stream_config,
|
|
14
|
+
)
|
|
9
15
|
from .event_listener import EventListener
|
|
10
|
-
from .dynamic_camera_manager import DynamicCameraManager
|
|
16
|
+
from .dynamic_camera_manager import DynamicCameraManager, DynamicCameraManagerForWorkers
|
|
17
|
+
|
|
18
|
+
USE_FFMPEG = os.getenv("USE_FFMPEG", "false").lower() == "true"
|
|
19
|
+
USE_GSTREAMER = os.getenv("USE_GSTREAMER", "false").lower() == "true"
|
|
20
|
+
USE_NVDEC = os.getenv("USE_NVDEC", "false").lower() == "true"
|
|
21
|
+
|
|
22
|
+
# GStreamer imports (optional - graceful degradation)
|
|
23
|
+
GSTREAMER_AVAILABLE = False
|
|
24
|
+
try:
|
|
25
|
+
from .camera_streamer.gstreamer_camera_streamer import (
|
|
26
|
+
GStreamerCameraStreamer,
|
|
27
|
+
GStreamerConfig,
|
|
28
|
+
is_gstreamer_available,
|
|
29
|
+
)
|
|
30
|
+
from .camera_streamer.gstreamer_worker_manager import GStreamerWorkerManager
|
|
31
|
+
GSTREAMER_AVAILABLE = is_gstreamer_available()
|
|
32
|
+
except (ImportError, ValueError):
|
|
33
|
+
# ImportError: gi module not available
|
|
34
|
+
# ValueError: gi.require_version fails when GStreamer not installed
|
|
35
|
+
pass
|
|
36
|
+
|
|
37
|
+
# FFmpeg imports (optional - graceful degradation)
|
|
38
|
+
FFMPEG_AVAILABLE = False
|
|
39
|
+
try:
|
|
40
|
+
from .camera_streamer.ffmpeg_config import FFmpegConfig, is_ffmpeg_available
|
|
41
|
+
from .camera_streamer.ffmpeg_camera_streamer import FFmpegCameraStreamer
|
|
42
|
+
from .camera_streamer.ffmpeg_worker_manager import FFmpegWorkerManager
|
|
43
|
+
FFMPEG_AVAILABLE = is_ffmpeg_available()
|
|
44
|
+
except (ImportError, FileNotFoundError):
|
|
45
|
+
# FFmpeg not available or not installed
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
# NVDEC imports (optional - graceful degradation)
|
|
49
|
+
NVDEC_AVAILABLE = False
|
|
50
|
+
try:
|
|
51
|
+
from .camera_streamer.nvdec_worker_manager import NVDECWorkerManager, is_nvdec_available
|
|
52
|
+
NVDEC_AVAILABLE = is_nvdec_available()
|
|
53
|
+
except ImportError:
|
|
54
|
+
# NVDEC not available (requires CuPy, PyNvVideoCodec)
|
|
55
|
+
pass
|
|
11
56
|
|
|
12
57
|
|
|
13
58
|
class StreamingGateway:
|
|
@@ -27,6 +72,43 @@ class StreamingGateway:
|
|
|
27
72
|
video_codec: Optional[str] = None,
|
|
28
73
|
force_restart: bool = False,
|
|
29
74
|
enable_event_listening: bool = True,
|
|
75
|
+
action_id: str = None,
|
|
76
|
+
use_async_workers: bool = True,
|
|
77
|
+
num_workers: int = None, # Auto-calculate based on CPU cores and camera count
|
|
78
|
+
max_cameras_per_worker: int = 50,
|
|
79
|
+
allow_empty_start: bool = True,
|
|
80
|
+
# GStreamer options
|
|
81
|
+
use_gstreamer: bool = False,
|
|
82
|
+
gstreamer_encoder: str = "auto", # auto, nvenc, x264, openh264, jpeg
|
|
83
|
+
gstreamer_codec: str = "h264", # h264, h265
|
|
84
|
+
gstreamer_preset: str = "low-latency", # NVENC preset
|
|
85
|
+
gstreamer_gpu_id: int = 0, # GPU device ID for NVENC
|
|
86
|
+
# Platform-specific GStreamer options
|
|
87
|
+
gstreamer_platform: str = "auto", # auto, jetson, desktop-gpu, intel, amd, cpu
|
|
88
|
+
gstreamer_use_hardware_decode: bool = True, # Use hardware decode (nvv4l2decoder, nvdec, vaapi)
|
|
89
|
+
gstreamer_use_hardware_jpeg: bool = True, # Use hardware JPEG (nvjpegenc, vaapijpegenc)
|
|
90
|
+
gstreamer_jetson_use_nvmm: bool = True, # Use NVMM zero-copy on Jetson
|
|
91
|
+
gstreamer_frame_optimizer_mode: str = "hash-only", # hash-only, dual-appsink, disabled
|
|
92
|
+
gstreamer_fallback_on_error: bool = True, # Gracefully fallback to CPU pipeline on error
|
|
93
|
+
gstreamer_verbose_logging: bool = False, # Verbose pipeline logging for debugging
|
|
94
|
+
# FFmpeg options
|
|
95
|
+
use_ffmpeg: bool = USE_FFMPEG, # Use FFmpeg subprocess-based encoding
|
|
96
|
+
ffmpeg_hwaccel: str = "auto", # Hardware acceleration: auto, cuda, vaapi, none
|
|
97
|
+
ffmpeg_threads: int = 1, # FFmpeg decode threads per stream
|
|
98
|
+
ffmpeg_low_latency: bool = True, # Enable low-latency flags
|
|
99
|
+
ffmpeg_pixel_format: str = "bgr24",# Output pixel format
|
|
100
|
+
# NVDEC options (CUDA IPC ring buffer output)
|
|
101
|
+
use_nvdec: bool = USE_NVDEC, # Use NVDEC hardware decode + CUDA IPC output
|
|
102
|
+
nvdec_gpu_id: int = 0, # Primary GPU device ID (starting GPU)
|
|
103
|
+
nvdec_num_gpus: int = 0, # Number of GPUs (0=auto-detect all available)
|
|
104
|
+
nvdec_pool_size: int = 8, # NVDEC decoders per GPU
|
|
105
|
+
nvdec_burst_size: int = 4, # Frames per stream before rotating
|
|
106
|
+
nvdec_frame_width: int = 640, # Output frame width
|
|
107
|
+
nvdec_frame_height: int = 640, # Output frame height
|
|
108
|
+
nvdec_num_slots: int = 32, # Ring buffer slots per camera
|
|
109
|
+
nvdec_target_fps: int = 0, # FPS override (0=use per-camera FPS from config)
|
|
110
|
+
# SHM configuration (centralized)
|
|
111
|
+
shm_slot_count: int = 1000, # Ring buffer size per camera (increased for consumer lag)
|
|
30
112
|
):
|
|
31
113
|
"""Initialize StreamingGateway.
|
|
32
114
|
|
|
@@ -39,6 +121,38 @@ class StreamingGateway:
|
|
|
39
121
|
video_codec: Video codec (h264 or h265)
|
|
40
122
|
force_restart: Force stop existing streams and restart
|
|
41
123
|
enable_event_listening: Enable dynamic event listening for configuration updates
|
|
124
|
+
action_id: Optional action ID to pass in API requests
|
|
125
|
+
use_async_workers: Use new async worker flow (default True)
|
|
126
|
+
num_workers: Number of worker processes for async flow
|
|
127
|
+
max_cameras_per_worker: Maximum cameras per worker process
|
|
128
|
+
allow_empty_start: Allow starting with zero cameras (default True)
|
|
129
|
+
use_gstreamer: Use GStreamer-based encoding (default False)
|
|
130
|
+
gstreamer_encoder: GStreamer encoder type (auto, nvenc, x264, openh264, jpeg)
|
|
131
|
+
gstreamer_codec: GStreamer codec (h264, h265)
|
|
132
|
+
gstreamer_preset: NVENC preset for hardware encoding
|
|
133
|
+
gstreamer_gpu_id: GPU device ID for NVENC hardware encoding
|
|
134
|
+
gstreamer_platform: Platform override (auto, jetson, desktop-gpu, intel, amd, cpu)
|
|
135
|
+
gstreamer_use_hardware_decode: Enable hardware decode (nvv4l2decoder, nvdec, vaapi)
|
|
136
|
+
gstreamer_use_hardware_jpeg: Enable hardware JPEG encoding when available
|
|
137
|
+
gstreamer_jetson_use_nvmm: Use NVMM zero-copy memory on Jetson devices
|
|
138
|
+
gstreamer_frame_optimizer_mode: Frame optimization mode (hash-only, dual-appsink, disabled)
|
|
139
|
+
gstreamer_fallback_on_error: Automatically fallback to CPU pipeline on hardware errors
|
|
140
|
+
gstreamer_verbose_logging: Enable verbose pipeline construction logging
|
|
141
|
+
use_ffmpeg: Use FFmpeg subprocess-based encoding (alternative to OpenCV/GStreamer)
|
|
142
|
+
ffmpeg_hwaccel: FFmpeg hardware acceleration (auto, cuda, vaapi, none)
|
|
143
|
+
ffmpeg_threads: Number of FFmpeg decode threads per stream
|
|
144
|
+
ffmpeg_low_latency: Enable FFmpeg low-latency flags
|
|
145
|
+
ffmpeg_pixel_format: Output pixel format (bgr24, rgb24, nv12)
|
|
146
|
+
use_nvdec: Use NVDEC hardware decode with CUDA IPC output (requires CuPy, PyNvVideoCodec)
|
|
147
|
+
nvdec_gpu_id: Primary/starting GPU device ID for round-robin camera assignment
|
|
148
|
+
nvdec_num_gpus: Number of GPUs to use (0=auto-detect all available GPUs)
|
|
149
|
+
nvdec_pool_size: Number of NVDEC decoders per GPU
|
|
150
|
+
nvdec_burst_size: Frames per stream before rotating to next stream
|
|
151
|
+
nvdec_frame_width: Default output frame width (used if camera config doesn't specify)
|
|
152
|
+
nvdec_frame_height: Default output frame height (used if camera config doesn't specify)
|
|
153
|
+
nvdec_num_slots: Ring buffer slots per camera (named by camera_id)
|
|
154
|
+
nvdec_target_fps: Global FPS override (0=use per-camera FPS from camera config)
|
|
155
|
+
shm_slot_count: Number of frame slots per camera ring buffer for SHM mode (default: 300)
|
|
42
156
|
"""
|
|
43
157
|
if not session:
|
|
44
158
|
raise ValueError("Session is required")
|
|
@@ -49,49 +163,383 @@ class StreamingGateway:
|
|
|
49
163
|
self.streaming_gateway_id = streaming_gateway_id
|
|
50
164
|
self.force_restart = force_restart
|
|
51
165
|
self.enable_event_listening = enable_event_listening
|
|
52
|
-
self.
|
|
166
|
+
self.use_async_workers = use_async_workers
|
|
167
|
+
self.num_workers = num_workers
|
|
168
|
+
self.max_cameras_per_worker = max_cameras_per_worker
|
|
169
|
+
self.video_codec = video_codec
|
|
170
|
+
|
|
171
|
+
# GStreamer configuration
|
|
172
|
+
self.use_gstreamer = use_gstreamer
|
|
173
|
+
self.gstreamer_encoder = gstreamer_encoder
|
|
174
|
+
self.gstreamer_codec = gstreamer_codec
|
|
175
|
+
self.gstreamer_preset = gstreamer_preset
|
|
176
|
+
self.gstreamer_gpu_id = gstreamer_gpu_id
|
|
177
|
+
# Platform-specific GStreamer configuration
|
|
178
|
+
self.gstreamer_platform = gstreamer_platform
|
|
179
|
+
self.gstreamer_use_hardware_decode = gstreamer_use_hardware_decode
|
|
180
|
+
self.gstreamer_use_hardware_jpeg = gstreamer_use_hardware_jpeg
|
|
181
|
+
self.gstreamer_jetson_use_nvmm = gstreamer_jetson_use_nvmm
|
|
182
|
+
self.gstreamer_frame_optimizer_mode = gstreamer_frame_optimizer_mode
|
|
183
|
+
self.gstreamer_fallback_on_error = gstreamer_fallback_on_error
|
|
184
|
+
self.gstreamer_verbose_logging = gstreamer_verbose_logging
|
|
185
|
+
|
|
186
|
+
# Validate GStreamer availability if requested
|
|
187
|
+
if use_gstreamer and not GSTREAMER_AVAILABLE:
|
|
188
|
+
raise RuntimeError(
|
|
189
|
+
"GStreamer requested but not available. "
|
|
190
|
+
"Install with: pip install PyGObject && apt-get install gstreamer1.0-plugins-*"
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
# FFmpeg configuration
|
|
194
|
+
self.use_ffmpeg = use_ffmpeg
|
|
195
|
+
self.ffmpeg_hwaccel = ffmpeg_hwaccel
|
|
196
|
+
self.ffmpeg_threads = ffmpeg_threads
|
|
197
|
+
self.ffmpeg_low_latency = ffmpeg_low_latency
|
|
198
|
+
self.ffmpeg_pixel_format = ffmpeg_pixel_format
|
|
199
|
+
|
|
200
|
+
# NVDEC configuration
|
|
201
|
+
self.use_nvdec = use_nvdec
|
|
202
|
+
self.nvdec_gpu_id = nvdec_gpu_id
|
|
203
|
+
self.nvdec_num_gpus = nvdec_num_gpus
|
|
204
|
+
self.nvdec_pool_size = nvdec_pool_size
|
|
205
|
+
self.nvdec_burst_size = nvdec_burst_size
|
|
206
|
+
self.nvdec_frame_width = nvdec_frame_width
|
|
207
|
+
self.nvdec_frame_height = nvdec_frame_height
|
|
208
|
+
self.nvdec_num_slots = nvdec_num_slots
|
|
209
|
+
self.nvdec_target_fps = nvdec_target_fps
|
|
210
|
+
|
|
211
|
+
# SHM configuration (centralized for all workers)
|
|
212
|
+
self.shm_slot_count = shm_slot_count
|
|
213
|
+
|
|
214
|
+
# Validate FFmpeg availability if requested
|
|
215
|
+
if use_ffmpeg and not FFMPEG_AVAILABLE:
|
|
216
|
+
raise RuntimeError(
|
|
217
|
+
"FFmpeg requested but not available. "
|
|
218
|
+
"Install FFmpeg from https://ffmpeg.org/download.html"
|
|
219
|
+
)
|
|
220
|
+
|
|
221
|
+
# Validate NVDEC availability if requested
|
|
222
|
+
if use_nvdec and not NVDEC_AVAILABLE:
|
|
223
|
+
raise RuntimeError(
|
|
224
|
+
"NVDEC requested but not available. "
|
|
225
|
+
"Requires CuPy, PyNvVideoCodec, and cuda_shm_ring_buffer module."
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
# Validate exclusive backend selection
|
|
229
|
+
backends_enabled = sum([use_gstreamer, use_ffmpeg, use_nvdec])
|
|
230
|
+
if backends_enabled > 1:
|
|
231
|
+
raise ValueError("Cannot enable multiple backends (GStreamer, FFmpeg, NVDEC) simultaneously")
|
|
53
232
|
|
|
54
233
|
# Initialize utility for API interactions
|
|
55
|
-
self.gateway_util = StreamingGatewayUtil(session, streaming_gateway_id, server_id)
|
|
234
|
+
self.gateway_util = StreamingGatewayUtil(session, streaming_gateway_id, server_id, action_id=action_id)
|
|
235
|
+
|
|
236
|
+
# Determine server_type - fetch from API if not provided
|
|
237
|
+
if server_type is None:
|
|
238
|
+
gateway_info = self.gateway_util.get_streaming_gateway_by_id()
|
|
239
|
+
if gateway_info:
|
|
240
|
+
server_type = gateway_info.get('serverType')
|
|
241
|
+
logging.info(f"Retrieved server_type from API: {server_type}")
|
|
242
|
+
else:
|
|
243
|
+
raise ValueError("server_type is required but could not be retrieved from API")
|
|
244
|
+
|
|
245
|
+
if not server_type:
|
|
246
|
+
raise ValueError("server_type is required (kafka or redis)")
|
|
247
|
+
|
|
248
|
+
self.server_type = server_type
|
|
249
|
+
self.allow_empty_start = allow_empty_start
|
|
56
250
|
|
|
57
251
|
# Get input configurations
|
|
58
252
|
if inputs_config is None:
|
|
59
253
|
logging.info("Fetching input configurations from API")
|
|
60
|
-
|
|
254
|
+
try:
|
|
255
|
+
self.inputs_config = self.gateway_util.get_input_streams()
|
|
256
|
+
except Exception as exc:
|
|
257
|
+
logging.warning(f"Failed to fetch cameras from API: {exc}")
|
|
258
|
+
if allow_empty_start:
|
|
259
|
+
logging.info("Continuing with zero cameras (allow_empty_start=True)")
|
|
260
|
+
self.inputs_config = []
|
|
261
|
+
else:
|
|
262
|
+
raise
|
|
61
263
|
else:
|
|
62
264
|
self.inputs_config = inputs_config if isinstance(inputs_config, list) else [inputs_config]
|
|
63
265
|
|
|
266
|
+
# Check if we have cameras
|
|
64
267
|
if not self.inputs_config:
|
|
65
|
-
|
|
268
|
+
if allow_empty_start:
|
|
269
|
+
logging.warning("Starting gateway with zero cameras - use camera_manager.add_camera() to add dynamically")
|
|
270
|
+
else:
|
|
271
|
+
raise ValueError("No input configurations available and allow_empty_start=False")
|
|
66
272
|
|
|
67
|
-
# Validate inputs
|
|
273
|
+
# Validate inputs (only if we have any)
|
|
68
274
|
for i, config in enumerate(self.inputs_config):
|
|
69
275
|
if not isinstance(config, InputStream):
|
|
70
276
|
raise ValueError(f"Input config {i} must be an InputStream instance")
|
|
71
277
|
|
|
72
|
-
# Initialize
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
278
|
+
# Initialize streaming backend based on configuration
|
|
279
|
+
# Options: use_nvdec, use_ffmpeg, use_gstreamer, use_async_workers (AsyncCameraWorker), or CameraStreamer
|
|
280
|
+
self.camera_streamer: Optional[CameraStreamer] = None
|
|
281
|
+
self.worker_manager: Optional[WorkerManager] = None
|
|
282
|
+
self.gstreamer_streamer: Optional[Any] = None # GStreamerCameraStreamer
|
|
283
|
+
self.gstreamer_worker_manager: Optional[Any] = None # GStreamerWorkerManager
|
|
284
|
+
self.ffmpeg_streamer: Optional[Any] = None # FFmpegCameraStreamer
|
|
285
|
+
self.ffmpeg_worker_manager: Optional[Any] = None # FFmpegWorkerManager
|
|
286
|
+
self.nvdec_worker_manager: Optional[Any] = None # NVDECWorkerManager
|
|
80
287
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
288
|
+
if self.use_nvdec:
|
|
289
|
+
# NVDEC-based streaming flow (CUDA IPC output, static camera config)
|
|
290
|
+
logging.info(
|
|
291
|
+
f"Initializing NVDEC worker flow - GPUs: {nvdec_num_gpus}, "
|
|
292
|
+
f"pool_size: {nvdec_pool_size}, output: NV12 ({nvdec_frame_width}x{nvdec_frame_height})"
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Build stream config (unused by NVDEC but needed for interface consistency)
|
|
296
|
+
stream_config = build_stream_config(
|
|
297
|
+
gateway_util=self.gateway_util,
|
|
298
|
+
server_type=server_type,
|
|
299
|
+
service_id=streaming_gateway_id,
|
|
300
|
+
stream_maxlen=self.shm_slot_count,
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# Convert InputStream configs to camera_config dicts
|
|
304
|
+
camera_configs = [
|
|
305
|
+
input_stream_to_camera_config(inp) for inp in self.inputs_config
|
|
306
|
+
]
|
|
307
|
+
|
|
308
|
+
self.nvdec_worker_manager = NVDECWorkerManager(
|
|
309
|
+
camera_configs=camera_configs,
|
|
310
|
+
stream_config=stream_config,
|
|
311
|
+
gpu_id=nvdec_gpu_id,
|
|
312
|
+
num_gpus=nvdec_num_gpus,
|
|
313
|
+
nvdec_pool_size=nvdec_pool_size,
|
|
314
|
+
nvdec_burst_size=nvdec_burst_size,
|
|
315
|
+
frame_width=nvdec_frame_width,
|
|
316
|
+
frame_height=nvdec_frame_height,
|
|
317
|
+
num_slots=nvdec_num_slots,
|
|
318
|
+
target_fps=nvdec_target_fps,
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# NVDEC uses static camera configuration - no dynamic camera manager
|
|
322
|
+
# Set camera_manager to None to indicate static mode
|
|
323
|
+
self.camera_manager = None
|
|
324
|
+
logging.info("NVDEC backend initialized (static camera configuration)")
|
|
325
|
+
|
|
326
|
+
elif self.use_ffmpeg:
|
|
327
|
+
# FFmpeg-based streaming flow
|
|
328
|
+
# Build stream config for workers
|
|
329
|
+
stream_config = build_stream_config(
|
|
330
|
+
gateway_util=self.gateway_util,
|
|
331
|
+
server_type=server_type,
|
|
332
|
+
service_id=streaming_gateway_id,
|
|
333
|
+
stream_maxlen=self.shm_slot_count,
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
# Create FFmpeg configuration
|
|
337
|
+
ffmpeg_config = FFmpegConfig(
|
|
338
|
+
hwaccel=ffmpeg_hwaccel,
|
|
339
|
+
threads=ffmpeg_threads,
|
|
340
|
+
low_latency=ffmpeg_low_latency,
|
|
341
|
+
pixel_format=ffmpeg_pixel_format,
|
|
342
|
+
)
|
|
343
|
+
|
|
344
|
+
if self.use_async_workers:
|
|
345
|
+
# FFmpeg with worker processes
|
|
346
|
+
logging.info(
|
|
347
|
+
f"Initializing FFmpeg worker flow - hwaccel: {ffmpeg_hwaccel}, "
|
|
348
|
+
f"threads: {ffmpeg_threads}"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Convert InputStream configs to camera_config dicts
|
|
352
|
+
camera_configs = [
|
|
353
|
+
input_stream_to_camera_config(inp) for inp in self.inputs_config
|
|
354
|
+
]
|
|
355
|
+
|
|
356
|
+
self.ffmpeg_worker_manager = FFmpegWorkerManager(
|
|
357
|
+
camera_configs=camera_configs,
|
|
358
|
+
stream_config=stream_config,
|
|
359
|
+
num_workers=num_workers,
|
|
360
|
+
max_cameras_per_worker=max_cameras_per_worker,
|
|
361
|
+
ffmpeg_config=ffmpeg_config,
|
|
362
|
+
shm_slot_count=self.shm_slot_count,
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
# Initialize dynamic camera manager for FFmpeg workers
|
|
366
|
+
self.camera_manager = DynamicCameraManagerForWorkers(
|
|
367
|
+
worker_manager=self.ffmpeg_worker_manager,
|
|
368
|
+
streaming_gateway_id=streaming_gateway_id,
|
|
369
|
+
session=self.session,
|
|
370
|
+
streaming_gateway=self,
|
|
371
|
+
)
|
|
372
|
+
else:
|
|
373
|
+
# FFmpeg single-threaded mode
|
|
374
|
+
logging.info(
|
|
375
|
+
f"Initializing FFmpeg CameraStreamer - hwaccel: {ffmpeg_hwaccel}"
|
|
376
|
+
)
|
|
377
|
+
|
|
378
|
+
self.ffmpeg_streamer = FFmpegCameraStreamer(
|
|
379
|
+
session=self.session,
|
|
380
|
+
service_id=streaming_gateway_id,
|
|
381
|
+
server_type=server_type,
|
|
382
|
+
video_codec=video_codec,
|
|
383
|
+
gateway_util=self.gateway_util,
|
|
384
|
+
ffmpeg_config=ffmpeg_config,
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
# Initialize dynamic camera manager for FFmpeg streamer
|
|
388
|
+
self.camera_manager = DynamicCameraManager(
|
|
389
|
+
camera_streamer=self.ffmpeg_streamer,
|
|
390
|
+
streaming_gateway_id=streaming_gateway_id,
|
|
391
|
+
session=self.session,
|
|
392
|
+
streaming_gateway=self,
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
elif self.use_gstreamer:
|
|
396
|
+
# GStreamer-based encoding flow
|
|
397
|
+
if self.use_async_workers:
|
|
398
|
+
# GStreamer with worker processes
|
|
399
|
+
logging.info(
|
|
400
|
+
f"Initializing GStreamer worker flow - encoder: {gstreamer_encoder}, "
|
|
401
|
+
f"codec: {gstreamer_codec}, gpu: {gstreamer_gpu_id}"
|
|
402
|
+
)
|
|
403
|
+
|
|
404
|
+
# Build stream config for workers
|
|
405
|
+
stream_config = build_stream_config(
|
|
406
|
+
gateway_util=self.gateway_util,
|
|
407
|
+
server_type=server_type,
|
|
408
|
+
service_id=streaming_gateway_id,
|
|
409
|
+
stream_maxlen=self.shm_slot_count,
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
# Convert InputStream configs to camera_config dicts
|
|
413
|
+
camera_configs = [
|
|
414
|
+
input_stream_to_camera_config(inp) for inp in self.inputs_config
|
|
415
|
+
]
|
|
416
|
+
|
|
417
|
+
self.gstreamer_worker_manager = GStreamerWorkerManager(
|
|
418
|
+
camera_configs=camera_configs,
|
|
419
|
+
stream_config=stream_config,
|
|
420
|
+
num_workers=num_workers,
|
|
421
|
+
max_cameras_per_worker=max_cameras_per_worker,
|
|
422
|
+
gstreamer_encoder=gstreamer_encoder,
|
|
423
|
+
gstreamer_codec=gstreamer_codec,
|
|
424
|
+
gstreamer_preset=gstreamer_preset,
|
|
425
|
+
gpu_id=gstreamer_gpu_id,
|
|
426
|
+
platform=gstreamer_platform,
|
|
427
|
+
use_hardware_decode=gstreamer_use_hardware_decode,
|
|
428
|
+
use_hardware_jpeg=gstreamer_use_hardware_jpeg,
|
|
429
|
+
jetson_use_nvmm=gstreamer_jetson_use_nvmm,
|
|
430
|
+
frame_optimizer_mode=gstreamer_frame_optimizer_mode,
|
|
431
|
+
fallback_on_error=gstreamer_fallback_on_error,
|
|
432
|
+
verbose_pipeline_logging=gstreamer_verbose_logging,
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
# Initialize dynamic camera manager for GStreamer workers
|
|
436
|
+
# Use the same interface as WorkerManager
|
|
437
|
+
self.camera_manager = DynamicCameraManagerForWorkers(
|
|
438
|
+
worker_manager=self.gstreamer_worker_manager,
|
|
439
|
+
streaming_gateway_id=streaming_gateway_id,
|
|
440
|
+
session=self.session,
|
|
441
|
+
streaming_gateway=self,
|
|
442
|
+
)
|
|
443
|
+
else:
|
|
444
|
+
# GStreamer single-threaded mode
|
|
445
|
+
logging.info(
|
|
446
|
+
f"Initializing GStreamer CameraStreamer - encoder: {gstreamer_encoder}, "
|
|
447
|
+
f"codec: {gstreamer_codec}, gpu: {gstreamer_gpu_id}"
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
gst_config = GStreamerConfig(
|
|
451
|
+
encoder=gstreamer_encoder,
|
|
452
|
+
codec=gstreamer_codec,
|
|
453
|
+
preset=gstreamer_preset,
|
|
454
|
+
gpu_id=gstreamer_gpu_id,
|
|
455
|
+
platform=gstreamer_platform,
|
|
456
|
+
use_hardware_decode=gstreamer_use_hardware_decode,
|
|
457
|
+
use_hardware_jpeg=gstreamer_use_hardware_jpeg,
|
|
458
|
+
jetson_use_nvmm=gstreamer_jetson_use_nvmm,
|
|
459
|
+
frame_optimizer_mode=gstreamer_frame_optimizer_mode,
|
|
460
|
+
fallback_on_error=gstreamer_fallback_on_error,
|
|
461
|
+
verbose_pipeline_logging=gstreamer_verbose_logging,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
self.gstreamer_streamer = GStreamerCameraStreamer(
|
|
465
|
+
session=self.session,
|
|
466
|
+
service_id=streaming_gateway_id,
|
|
467
|
+
server_type=server_type,
|
|
468
|
+
video_codec=video_codec,
|
|
469
|
+
gateway_util=self.gateway_util,
|
|
470
|
+
gstreamer_config=gst_config,
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
# Initialize dynamic camera manager for GStreamer streamer
|
|
474
|
+
# GStreamerCameraStreamer has the same API as CameraStreamer
|
|
475
|
+
self.camera_manager = DynamicCameraManager(
|
|
476
|
+
camera_streamer=self.gstreamer_streamer,
|
|
477
|
+
streaming_gateway_id=streaming_gateway_id,
|
|
478
|
+
session=self.session,
|
|
479
|
+
streaming_gateway=self,
|
|
480
|
+
)
|
|
481
|
+
|
|
482
|
+
elif self.use_async_workers:
|
|
483
|
+
# New async worker flow using WorkerManager
|
|
484
|
+
logging.info("Initializing async worker flow with WorkerManager")
|
|
485
|
+
|
|
486
|
+
# Build stream config for workers
|
|
487
|
+
stream_config = build_stream_config(
|
|
488
|
+
gateway_util=self.gateway_util,
|
|
489
|
+
server_type=server_type,
|
|
490
|
+
service_id=streaming_gateway_id,
|
|
491
|
+
stream_maxlen=self.shm_slot_count,
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
# Convert InputStream configs to camera_config dicts
|
|
495
|
+
camera_configs = [
|
|
496
|
+
input_stream_to_camera_config(inp) for inp in self.inputs_config
|
|
497
|
+
]
|
|
498
|
+
|
|
499
|
+
self.worker_manager = WorkerManager(
|
|
500
|
+
camera_configs=camera_configs,
|
|
501
|
+
stream_config=stream_config,
|
|
502
|
+
num_workers=num_workers,
|
|
503
|
+
max_cameras_per_worker=max_cameras_per_worker,
|
|
504
|
+
shm_slot_count=self.shm_slot_count,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# Initialize dynamic camera manager for workers
|
|
508
|
+
self.camera_manager = DynamicCameraManagerForWorkers(
|
|
509
|
+
worker_manager=self.worker_manager,
|
|
510
|
+
streaming_gateway_id=streaming_gateway_id,
|
|
511
|
+
session=self.session,
|
|
512
|
+
streaming_gateway=self,
|
|
513
|
+
)
|
|
514
|
+
else:
|
|
515
|
+
# Original CameraStreamer flow
|
|
516
|
+
logging.info("Initializing original CameraStreamer flow")
|
|
517
|
+
self.camera_streamer = CameraStreamer(
|
|
518
|
+
session=self.session,
|
|
519
|
+
service_id=streaming_gateway_id,
|
|
520
|
+
server_type=server_type,
|
|
521
|
+
video_codec=video_codec,
|
|
522
|
+
gateway_util=self.gateway_util,
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
# Initialize dynamic camera manager for CameraStreamer
|
|
526
|
+
self.camera_manager = DynamicCameraManager(
|
|
527
|
+
camera_streamer=self.camera_streamer,
|
|
528
|
+
streaming_gateway_id=streaming_gateway_id,
|
|
529
|
+
session=self.session,
|
|
530
|
+
streaming_gateway=self,
|
|
531
|
+
)
|
|
87
532
|
|
|
88
533
|
# Initialize with current camera configurations
|
|
89
|
-
|
|
534
|
+
# (skip for NVDEC which uses static configuration)
|
|
535
|
+
if self.camera_manager is not None:
|
|
536
|
+
self.camera_manager.initialize_from_config(self.inputs_config)
|
|
90
537
|
|
|
91
|
-
# Initialize event system (if enabled)
|
|
538
|
+
# Initialize event system (if enabled and camera_manager exists)
|
|
539
|
+
# NVDEC doesn't support dynamic cameras, so event listening is disabled
|
|
92
540
|
self.event_listener: Optional[EventListener] = None
|
|
93
|
-
|
|
94
|
-
if self.enable_event_listening:
|
|
541
|
+
|
|
542
|
+
if self.enable_event_listening and self.camera_manager is not None:
|
|
95
543
|
try:
|
|
96
544
|
self.event_listener = EventListener(
|
|
97
545
|
session=self.session,
|
|
@@ -101,12 +549,15 @@ class StreamingGateway:
|
|
|
101
549
|
except Exception as e:
|
|
102
550
|
logging.warning(f"Could not initialize event system: {e}")
|
|
103
551
|
logging.info("Continuing without event listening")
|
|
552
|
+
elif self.enable_event_listening and self.use_nvdec:
|
|
553
|
+
logging.info("Event listening disabled for NVDEC backend (static camera configuration)")
|
|
104
554
|
|
|
105
555
|
# State management
|
|
106
556
|
self.is_streaming = False
|
|
107
557
|
self._stop_event = threading.Event()
|
|
108
558
|
self._state_lock = threading.RLock()
|
|
109
559
|
self._my_stream_keys = set()
|
|
560
|
+
self._stream_key_to_camera_id = {} # Mapping of stream_key -> camera_id
|
|
110
561
|
self._cleanup_registered = False
|
|
111
562
|
|
|
112
563
|
# Statistics
|
|
@@ -163,9 +614,13 @@ class StreamingGateway:
|
|
|
163
614
|
logging.warning("Streaming is already active")
|
|
164
615
|
return False
|
|
165
616
|
|
|
617
|
+
# Check if we have cameras (allow empty if flag is set)
|
|
166
618
|
if not self.inputs_config:
|
|
167
|
-
|
|
168
|
-
|
|
619
|
+
if self.allow_empty_start:
|
|
620
|
+
logging.warning("Starting streaming with zero cameras - awaiting dynamic camera addition")
|
|
621
|
+
else:
|
|
622
|
+
logging.error("No input configurations available")
|
|
623
|
+
return False
|
|
169
624
|
|
|
170
625
|
# Force stop existing streams if requested
|
|
171
626
|
self._stop_existing_streams()
|
|
@@ -173,45 +628,26 @@ class StreamingGateway:
|
|
|
173
628
|
# Register as active
|
|
174
629
|
self._register_as_active()
|
|
175
630
|
|
|
176
|
-
# Start streaming for each input
|
|
177
|
-
started_streams = []
|
|
178
631
|
try:
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
fps=input_config.fps,
|
|
196
|
-
stream_key=stream_key,
|
|
197
|
-
stream_group_key=input_config.camera_group_key,
|
|
198
|
-
quality=input_config.quality,
|
|
199
|
-
width=input_config.width,
|
|
200
|
-
height=input_config.height,
|
|
201
|
-
simulate_video_file_stream=input_config.simulate_video_file_stream,
|
|
202
|
-
camera_location=input_config.camera_location,
|
|
203
|
-
)
|
|
632
|
+
if self.use_nvdec:
|
|
633
|
+
success = self._start_nvdec_worker_streaming()
|
|
634
|
+
elif self.use_ffmpeg:
|
|
635
|
+
if self.use_async_workers:
|
|
636
|
+
success = self._start_ffmpeg_worker_streaming()
|
|
637
|
+
else:
|
|
638
|
+
success = self._start_ffmpeg_streamer_streaming()
|
|
639
|
+
elif self.use_gstreamer:
|
|
640
|
+
if self.use_async_workers:
|
|
641
|
+
success = self._start_gstreamer_worker_streaming()
|
|
642
|
+
else:
|
|
643
|
+
success = self._start_gstreamer_streamer_streaming()
|
|
644
|
+
elif self.use_async_workers:
|
|
645
|
+
success = self._start_async_worker_streaming()
|
|
646
|
+
else:
|
|
647
|
+
success = self._start_camera_streamer_streaming()
|
|
204
648
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
if started_streams:
|
|
208
|
-
logging.info("Stopping already started streams")
|
|
209
|
-
self.stop_streaming()
|
|
210
|
-
return False
|
|
211
|
-
|
|
212
|
-
started_streams.append(stream_key)
|
|
213
|
-
self._my_stream_keys.add(stream_key)
|
|
214
|
-
logging.info(f"Started streaming for camera: {input_config.camera_key}")
|
|
649
|
+
if not success:
|
|
650
|
+
return False
|
|
215
651
|
|
|
216
652
|
with self._state_lock:
|
|
217
653
|
self._stop_event.clear()
|
|
@@ -235,6 +671,314 @@ class StreamingGateway:
|
|
|
235
671
|
logging.error(f"Error during cleanup: {cleanup_exc}")
|
|
236
672
|
return False
|
|
237
673
|
|
|
674
|
+
def _start_async_worker_streaming(self) -> bool:
|
|
675
|
+
"""Start streaming using new async worker flow.
|
|
676
|
+
|
|
677
|
+
Returns:
|
|
678
|
+
bool: True if started successfully, False otherwise
|
|
679
|
+
"""
|
|
680
|
+
num_cameras = len(self.inputs_config) if self.inputs_config else 0
|
|
681
|
+
logging.info(f"Starting async worker streaming flow with {num_cameras} cameras")
|
|
682
|
+
|
|
683
|
+
# Build stream key mappings (if we have cameras)
|
|
684
|
+
if self.inputs_config:
|
|
685
|
+
for i, input_config in enumerate(self.inputs_config):
|
|
686
|
+
stream_key = input_config.camera_key or f"stream_{i}"
|
|
687
|
+
camera_id = input_config.camera_id or stream_key
|
|
688
|
+
self._stream_key_to_camera_id[stream_key] = camera_id
|
|
689
|
+
self._my_stream_keys.add(stream_key)
|
|
690
|
+
|
|
691
|
+
# Start the worker manager (this starts all worker processes)
|
|
692
|
+
# WorkerManager handles empty camera lists gracefully
|
|
693
|
+
try:
|
|
694
|
+
self.worker_manager.start()
|
|
695
|
+
logging.info(f"Started WorkerManager with {self.num_workers} workers, {num_cameras} cameras")
|
|
696
|
+
return True
|
|
697
|
+
except Exception as exc:
|
|
698
|
+
logging.error(f"Failed to start WorkerManager: {exc}", exc_info=True)
|
|
699
|
+
return False
|
|
700
|
+
|
|
701
|
+
def _start_camera_streamer_streaming(self) -> bool:
|
|
702
|
+
"""Start streaming using original CameraStreamer flow.
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
bool: True if started successfully, False otherwise
|
|
706
|
+
"""
|
|
707
|
+
num_cameras = len(self.inputs_config) if self.inputs_config else 0
|
|
708
|
+
logging.info(f"Starting CameraStreamer streaming flow with {num_cameras} cameras")
|
|
709
|
+
|
|
710
|
+
# If no cameras, just return success (infrastructure is ready for dynamic cameras)
|
|
711
|
+
if not self.inputs_config:
|
|
712
|
+
logging.info("No cameras to start - awaiting dynamic camera addition")
|
|
713
|
+
return True
|
|
714
|
+
|
|
715
|
+
started_streams = []
|
|
716
|
+
|
|
717
|
+
for i, input_config in enumerate(self.inputs_config):
|
|
718
|
+
stream_key = input_config.camera_key or f"stream_{i}"
|
|
719
|
+
|
|
720
|
+
# Store camera_id mapping for metrics
|
|
721
|
+
camera_id = input_config.camera_id or stream_key
|
|
722
|
+
self._stream_key_to_camera_id[stream_key] = camera_id
|
|
723
|
+
|
|
724
|
+
# Register topic - generate default if not provided
|
|
725
|
+
topic = input_config.camera_input_topic
|
|
726
|
+
if not topic:
|
|
727
|
+
# Generate default topic name
|
|
728
|
+
topic = f"{camera_id}_input_topic"
|
|
729
|
+
logging.warning(f"No input topic for camera {input_config.camera_key}, using default: {topic}")
|
|
730
|
+
|
|
731
|
+
self.camera_streamer.register_stream_topic(stream_key, topic)
|
|
732
|
+
|
|
733
|
+
# Start streaming
|
|
734
|
+
success = self.camera_streamer.start_background_stream(
|
|
735
|
+
input=input_config.source,
|
|
736
|
+
fps=input_config.fps,
|
|
737
|
+
stream_key=stream_key,
|
|
738
|
+
stream_group_key=input_config.camera_group_key,
|
|
739
|
+
quality=input_config.quality,
|
|
740
|
+
width=input_config.width,
|
|
741
|
+
height=input_config.height,
|
|
742
|
+
simulate_video_file_stream=input_config.simulate_video_file_stream,
|
|
743
|
+
camera_location=input_config.camera_location,
|
|
744
|
+
)
|
|
745
|
+
|
|
746
|
+
if not success:
|
|
747
|
+
logging.error(f"Failed to start streaming for {input_config.source}")
|
|
748
|
+
if started_streams:
|
|
749
|
+
logging.info("Stopping already started streams")
|
|
750
|
+
self.stop_streaming()
|
|
751
|
+
return False
|
|
752
|
+
|
|
753
|
+
started_streams.append(stream_key)
|
|
754
|
+
self._my_stream_keys.add(stream_key)
|
|
755
|
+
logging.info(f"Started streaming for camera: {input_config.camera_key}")
|
|
756
|
+
|
|
757
|
+
return True
|
|
758
|
+
|
|
759
|
+
def _start_gstreamer_worker_streaming(self) -> bool:
|
|
760
|
+
"""Start streaming using GStreamer worker processes.
|
|
761
|
+
|
|
762
|
+
Returns:
|
|
763
|
+
bool: True if started successfully, False otherwise
|
|
764
|
+
"""
|
|
765
|
+
num_cameras = len(self.inputs_config) if self.inputs_config else 0
|
|
766
|
+
logging.info(
|
|
767
|
+
f"Starting GStreamer worker streaming with {num_cameras} cameras "
|
|
768
|
+
f"(encoder: {self.gstreamer_encoder}, codec: {self.gstreamer_codec})"
|
|
769
|
+
)
|
|
770
|
+
|
|
771
|
+
# Build stream key mappings
|
|
772
|
+
if self.inputs_config:
|
|
773
|
+
for i, input_config in enumerate(self.inputs_config):
|
|
774
|
+
stream_key = input_config.camera_key or f"stream_{i}"
|
|
775
|
+
camera_id = input_config.camera_id or stream_key
|
|
776
|
+
self._stream_key_to_camera_id[stream_key] = camera_id
|
|
777
|
+
self._my_stream_keys.add(stream_key)
|
|
778
|
+
|
|
779
|
+
# Start the GStreamer worker manager
|
|
780
|
+
try:
|
|
781
|
+
self.gstreamer_worker_manager.start()
|
|
782
|
+
logging.info(
|
|
783
|
+
f"Started GStreamerWorkerManager with {self.num_workers} workers, "
|
|
784
|
+
f"{num_cameras} cameras"
|
|
785
|
+
)
|
|
786
|
+
return True
|
|
787
|
+
except Exception as exc:
|
|
788
|
+
logging.error(f"Failed to start GStreamerWorkerManager: {exc}", exc_info=True)
|
|
789
|
+
return False
|
|
790
|
+
|
|
791
|
+
def _start_gstreamer_streamer_streaming(self) -> bool:
|
|
792
|
+
"""Start streaming using GStreamer CameraStreamer (single-threaded).
|
|
793
|
+
|
|
794
|
+
Returns:
|
|
795
|
+
bool: True if started successfully, False otherwise
|
|
796
|
+
"""
|
|
797
|
+
num_cameras = len(self.inputs_config) if self.inputs_config else 0
|
|
798
|
+
logging.info(
|
|
799
|
+
f"Starting GStreamer CameraStreamer with {num_cameras} cameras "
|
|
800
|
+
f"(encoder: {self.gstreamer_encoder}, codec: {self.gstreamer_codec})"
|
|
801
|
+
)
|
|
802
|
+
|
|
803
|
+
# If no cameras, return success (ready for dynamic cameras)
|
|
804
|
+
if not self.inputs_config:
|
|
805
|
+
logging.info("No cameras to start - awaiting dynamic camera addition")
|
|
806
|
+
return True
|
|
807
|
+
|
|
808
|
+
started_streams = []
|
|
809
|
+
|
|
810
|
+
for i, input_config in enumerate(self.inputs_config):
|
|
811
|
+
stream_key = input_config.camera_key or f"stream_{i}"
|
|
812
|
+
|
|
813
|
+
# Store camera_id mapping
|
|
814
|
+
camera_id = input_config.camera_id or stream_key
|
|
815
|
+
self._stream_key_to_camera_id[stream_key] = camera_id
|
|
816
|
+
|
|
817
|
+
# Register topic
|
|
818
|
+
topic = input_config.camera_input_topic
|
|
819
|
+
if not topic:
|
|
820
|
+
topic = f"{camera_id}_input_topic"
|
|
821
|
+
logging.warning(f"No input topic for camera {input_config.camera_key}, using default: {topic}")
|
|
822
|
+
|
|
823
|
+
self.gstreamer_streamer.register_stream_topic(stream_key, topic)
|
|
824
|
+
|
|
825
|
+
# Start streaming
|
|
826
|
+
success = self.gstreamer_streamer.start_background_stream(
|
|
827
|
+
input=input_config.source,
|
|
828
|
+
fps=input_config.fps,
|
|
829
|
+
stream_key=stream_key,
|
|
830
|
+
stream_group_key=input_config.camera_group_key,
|
|
831
|
+
quality=input_config.quality,
|
|
832
|
+
width=input_config.width,
|
|
833
|
+
height=input_config.height,
|
|
834
|
+
simulate_video_file_stream=input_config.simulate_video_file_stream,
|
|
835
|
+
camera_location=input_config.camera_location,
|
|
836
|
+
)
|
|
837
|
+
|
|
838
|
+
if not success:
|
|
839
|
+
logging.error(f"Failed to start GStreamer streaming for {input_config.source}")
|
|
840
|
+
if started_streams:
|
|
841
|
+
logging.info("Stopping already started streams")
|
|
842
|
+
self.stop_streaming()
|
|
843
|
+
return False
|
|
844
|
+
|
|
845
|
+
started_streams.append(stream_key)
|
|
846
|
+
self._my_stream_keys.add(stream_key)
|
|
847
|
+
logging.info(f"Started GStreamer streaming for camera: {input_config.camera_key}")
|
|
848
|
+
|
|
849
|
+
return True
|
|
850
|
+
|
|
851
|
+
def _start_nvdec_worker_streaming(self) -> bool:
|
|
852
|
+
"""Start streaming using NVDEC hardware decode with CUDA IPC output.
|
|
853
|
+
|
|
854
|
+
NVDEC outputs NV12 frames to CUDA IPC ring buffers for zero-copy
|
|
855
|
+
GPU inference pipelines. Unlike other backends, NVDEC:
|
|
856
|
+
- Uses static camera configuration (no dynamic add/remove)
|
|
857
|
+
- Outputs to CUDA IPC ring buffers (not Redis/Kafka)
|
|
858
|
+
- Outputs NV12 format (50% smaller than RGB)
|
|
859
|
+
|
|
860
|
+
Returns:
|
|
861
|
+
bool: True if started successfully, False otherwise
|
|
862
|
+
"""
|
|
863
|
+
num_cameras = len(self.inputs_config) if self.inputs_config else 0
|
|
864
|
+
logging.info(
|
|
865
|
+
f"Starting NVDEC worker streaming with {num_cameras} cameras "
|
|
866
|
+
f"(GPUs: {self.nvdec_num_gpus}, pool_size: {self.nvdec_pool_size}, "
|
|
867
|
+
f"output: NV12 {self.nvdec_frame_width}x{self.nvdec_frame_height})"
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
# Build stream key mappings for tracking
|
|
871
|
+
if self.inputs_config:
|
|
872
|
+
for i, input_config in enumerate(self.inputs_config):
|
|
873
|
+
stream_key = input_config.camera_key or f"stream_{i}"
|
|
874
|
+
camera_id = input_config.camera_id or stream_key
|
|
875
|
+
self._stream_key_to_camera_id[stream_key] = camera_id
|
|
876
|
+
self._my_stream_keys.add(stream_key)
|
|
877
|
+
|
|
878
|
+
# Start the NVDEC worker manager
|
|
879
|
+
try:
|
|
880
|
+
self.nvdec_worker_manager.start()
|
|
881
|
+
logging.info(
|
|
882
|
+
f"Started NVDECWorkerManager with {self.nvdec_num_gpus} GPU(s), "
|
|
883
|
+
f"{num_cameras} cameras"
|
|
884
|
+
)
|
|
885
|
+
return True
|
|
886
|
+
except Exception as exc:
|
|
887
|
+
logging.error(f"Failed to start NVDECWorkerManager: {exc}", exc_info=True)
|
|
888
|
+
return False
|
|
889
|
+
|
|
890
|
+
def _start_ffmpeg_worker_streaming(self) -> bool:
|
|
891
|
+
"""Start streaming using FFmpeg worker processes.
|
|
892
|
+
|
|
893
|
+
Returns:
|
|
894
|
+
bool: True if started successfully, False otherwise
|
|
895
|
+
"""
|
|
896
|
+
num_cameras = len(self.inputs_config) if self.inputs_config else 0
|
|
897
|
+
logging.info(
|
|
898
|
+
f"Starting FFmpeg worker streaming with {num_cameras} cameras "
|
|
899
|
+
f"(hwaccel: {self.ffmpeg_hwaccel}, threads: {self.ffmpeg_threads})"
|
|
900
|
+
)
|
|
901
|
+
|
|
902
|
+
# Build stream key mappings
|
|
903
|
+
if self.inputs_config:
|
|
904
|
+
for i, input_config in enumerate(self.inputs_config):
|
|
905
|
+
stream_key = input_config.camera_key or f"stream_{i}"
|
|
906
|
+
camera_id = input_config.camera_id or stream_key
|
|
907
|
+
self._stream_key_to_camera_id[stream_key] = camera_id
|
|
908
|
+
self._my_stream_keys.add(stream_key)
|
|
909
|
+
|
|
910
|
+
# Start the FFmpeg worker manager
|
|
911
|
+
try:
|
|
912
|
+
self.ffmpeg_worker_manager.start()
|
|
913
|
+
logging.info(
|
|
914
|
+
f"Started FFmpegWorkerManager with {self.num_workers} workers, "
|
|
915
|
+
f"{num_cameras} cameras"
|
|
916
|
+
)
|
|
917
|
+
return True
|
|
918
|
+
except Exception as exc:
|
|
919
|
+
logging.error(f"Failed to start FFmpegWorkerManager: {exc}", exc_info=True)
|
|
920
|
+
return False
|
|
921
|
+
|
|
922
|
+
def _start_ffmpeg_streamer_streaming(self) -> bool:
|
|
923
|
+
"""Start streaming using FFmpeg CameraStreamer (single-threaded).
|
|
924
|
+
|
|
925
|
+
Returns:
|
|
926
|
+
bool: True if started successfully, False otherwise
|
|
927
|
+
"""
|
|
928
|
+
num_cameras = len(self.inputs_config) if self.inputs_config else 0
|
|
929
|
+
logging.info(
|
|
930
|
+
f"Starting FFmpeg CameraStreamer with {num_cameras} cameras "
|
|
931
|
+
f"(hwaccel: {self.ffmpeg_hwaccel})"
|
|
932
|
+
)
|
|
933
|
+
|
|
934
|
+
# If no cameras, return success (ready for dynamic cameras)
|
|
935
|
+
if not self.inputs_config:
|
|
936
|
+
logging.info("No cameras to start - awaiting dynamic camera addition")
|
|
937
|
+
return True
|
|
938
|
+
|
|
939
|
+
started_streams = []
|
|
940
|
+
|
|
941
|
+
for i, input_config in enumerate(self.inputs_config):
|
|
942
|
+
stream_key = input_config.camera_key or f"stream_{i}"
|
|
943
|
+
|
|
944
|
+
# Store camera_id mapping
|
|
945
|
+
camera_id = input_config.camera_id or stream_key
|
|
946
|
+
self._stream_key_to_camera_id[stream_key] = camera_id
|
|
947
|
+
|
|
948
|
+
# Register topic
|
|
949
|
+
topic = input_config.camera_input_topic
|
|
950
|
+
if not topic:
|
|
951
|
+
topic = f"{camera_id}_input_topic"
|
|
952
|
+
logging.warning(f"No input topic for camera {input_config.camera_key}, using default: {topic}")
|
|
953
|
+
|
|
954
|
+
self.ffmpeg_streamer.register_stream_topic(stream_key, topic)
|
|
955
|
+
|
|
956
|
+
# Start streaming
|
|
957
|
+
success = self.ffmpeg_streamer.start_background_stream(
|
|
958
|
+
input=input_config.source,
|
|
959
|
+
fps=input_config.fps,
|
|
960
|
+
stream_key=stream_key,
|
|
961
|
+
stream_group_key=input_config.camera_group_key,
|
|
962
|
+
quality=input_config.quality,
|
|
963
|
+
width=input_config.width,
|
|
964
|
+
height=input_config.height,
|
|
965
|
+
simulate_video_file_stream=input_config.simulate_video_file_stream,
|
|
966
|
+
camera_location=input_config.camera_location,
|
|
967
|
+
)
|
|
968
|
+
|
|
969
|
+
if not success:
|
|
970
|
+
logging.error(f"Failed to start FFmpeg streaming for {input_config.source}")
|
|
971
|
+
if started_streams:
|
|
972
|
+
logging.info("Stopping already started streams")
|
|
973
|
+
self.stop_streaming()
|
|
974
|
+
return False
|
|
975
|
+
|
|
976
|
+
started_streams.append(stream_key)
|
|
977
|
+
self._my_stream_keys.add(stream_key)
|
|
978
|
+
logging.info(f"Started FFmpeg streaming for camera: {input_config.camera_key}")
|
|
979
|
+
|
|
980
|
+
return True
|
|
981
|
+
|
|
238
982
|
def stop_streaming(self):
|
|
239
983
|
"""Stop all streaming operations."""
|
|
240
984
|
with self._state_lock:
|
|
@@ -255,12 +999,94 @@ class StreamingGateway:
|
|
|
255
999
|
except Exception as exc:
|
|
256
1000
|
logging.error(f"Error stopping event listener: {exc}")
|
|
257
1001
|
|
|
258
|
-
# Stop
|
|
259
|
-
if self.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
1002
|
+
# Stop streaming backend based on which flow is active
|
|
1003
|
+
if self.use_nvdec:
|
|
1004
|
+
# Stop NVDEC backend
|
|
1005
|
+
if self.nvdec_worker_manager:
|
|
1006
|
+
try:
|
|
1007
|
+
logging.info("Stopping NVDECWorkerManager")
|
|
1008
|
+
self.nvdec_worker_manager.stop()
|
|
1009
|
+
logging.info("NVDEC worker manager stopped")
|
|
1010
|
+
except Exception as exc:
|
|
1011
|
+
logging.error(f"Error stopping NVDECWorkerManager: {exc}")
|
|
1012
|
+
elif self.use_ffmpeg:
|
|
1013
|
+
# Stop FFmpeg backends
|
|
1014
|
+
if self.use_async_workers:
|
|
1015
|
+
# Stop FFmpegWorkerManager
|
|
1016
|
+
if self.ffmpeg_worker_manager:
|
|
1017
|
+
try:
|
|
1018
|
+
logging.info("Stopping FFmpegWorkerManager")
|
|
1019
|
+
self.ffmpeg_worker_manager.stop()
|
|
1020
|
+
logging.info("FFmpeg worker manager stopped")
|
|
1021
|
+
except Exception as exc:
|
|
1022
|
+
logging.error(f"Error stopping FFmpegWorkerManager: {exc}")
|
|
1023
|
+
else:
|
|
1024
|
+
# Stop FFmpegCameraStreamer
|
|
1025
|
+
if self.ffmpeg_streamer:
|
|
1026
|
+
try:
|
|
1027
|
+
logging.info("Stopping FFmpegCameraStreamer")
|
|
1028
|
+
self.ffmpeg_streamer.stop_streaming()
|
|
1029
|
+
# Reset statistics for clean restart
|
|
1030
|
+
self.ffmpeg_streamer.reset_transmission_stats()
|
|
1031
|
+
# Clear pipeline references
|
|
1032
|
+
if hasattr(self.ffmpeg_streamer, 'pipelines'):
|
|
1033
|
+
self.ffmpeg_streamer.pipelines.clear()
|
|
1034
|
+
# Join streaming threads with timeout
|
|
1035
|
+
if hasattr(self.ffmpeg_streamer, 'streaming_threads'):
|
|
1036
|
+
for thread in self.ffmpeg_streamer.streaming_threads:
|
|
1037
|
+
if thread.is_alive():
|
|
1038
|
+
thread.join(timeout=5.0)
|
|
1039
|
+
self.ffmpeg_streamer.streaming_threads.clear()
|
|
1040
|
+
logging.info("FFmpeg cleanup completed")
|
|
1041
|
+
except Exception as exc:
|
|
1042
|
+
logging.error(f"Error stopping FFmpeg streaming: {exc}")
|
|
1043
|
+
elif self.use_gstreamer:
|
|
1044
|
+
# Stop GStreamer backends
|
|
1045
|
+
if self.use_async_workers:
|
|
1046
|
+
# Stop GStreamerWorkerManager
|
|
1047
|
+
if self.gstreamer_worker_manager:
|
|
1048
|
+
try:
|
|
1049
|
+
logging.info("Stopping GStreamerWorkerManager")
|
|
1050
|
+
self.gstreamer_worker_manager.stop()
|
|
1051
|
+
# Reset statistics for clean restart
|
|
1052
|
+
logging.info("Resetting GStreamer worker manager statistics")
|
|
1053
|
+
except Exception as exc:
|
|
1054
|
+
logging.error(f"Error stopping GStreamerWorkerManager: {exc}")
|
|
1055
|
+
else:
|
|
1056
|
+
# Stop GStreamerCameraStreamer
|
|
1057
|
+
if self.gstreamer_streamer:
|
|
1058
|
+
try:
|
|
1059
|
+
logging.info("Stopping GStreamerCameraStreamer")
|
|
1060
|
+
self.gstreamer_streamer.stop_streaming()
|
|
1061
|
+
# Reset statistics for clean restart
|
|
1062
|
+
self.gstreamer_streamer.reset_transmission_stats()
|
|
1063
|
+
# Clear pipeline references
|
|
1064
|
+
if hasattr(self.gstreamer_streamer, 'pipelines'):
|
|
1065
|
+
self.gstreamer_streamer.pipelines.clear()
|
|
1066
|
+
# Join streaming threads with timeout
|
|
1067
|
+
if hasattr(self.gstreamer_streamer, 'streaming_threads'):
|
|
1068
|
+
for thread in self.gstreamer_streamer.streaming_threads:
|
|
1069
|
+
if thread.is_alive():
|
|
1070
|
+
thread.join(timeout=5.0)
|
|
1071
|
+
self.gstreamer_streamer.streaming_threads.clear()
|
|
1072
|
+
logging.info("GStreamer cleanup completed")
|
|
1073
|
+
except Exception as exc:
|
|
1074
|
+
logging.error(f"Error stopping GStreamer streaming: {exc}")
|
|
1075
|
+
elif self.use_async_workers:
|
|
1076
|
+
# Stop WorkerManager
|
|
1077
|
+
if self.worker_manager:
|
|
1078
|
+
try:
|
|
1079
|
+
logging.info("Stopping WorkerManager")
|
|
1080
|
+
self.worker_manager.stop()
|
|
1081
|
+
except Exception as exc:
|
|
1082
|
+
logging.error(f"Error stopping WorkerManager: {exc}")
|
|
1083
|
+
else:
|
|
1084
|
+
# Stop CameraStreamer
|
|
1085
|
+
if self.camera_streamer:
|
|
1086
|
+
try:
|
|
1087
|
+
self.camera_streamer.stop_streaming()
|
|
1088
|
+
except Exception as exc:
|
|
1089
|
+
logging.error(f"Error stopping camera streaming: {exc}")
|
|
264
1090
|
|
|
265
1091
|
# Always attempt to update status to "stopped", even if other steps fail
|
|
266
1092
|
# This is critical for proper gateway lifecycle management
|
|
@@ -295,6 +1121,10 @@ class StreamingGateway:
|
|
|
295
1121
|
|
|
296
1122
|
logging.info(f"Streaming stopped (status updated: {status_updated})")
|
|
297
1123
|
|
|
1124
|
+
def get_camera_id_for_stream_key(self, stream_key: str) -> Optional[str]:
|
|
1125
|
+
"""Get camera_id for a given stream_key."""
|
|
1126
|
+
return self._stream_key_to_camera_id.get(stream_key)
|
|
1127
|
+
|
|
298
1128
|
def get_statistics(self) -> Dict:
|
|
299
1129
|
"""Get streaming statistics."""
|
|
300
1130
|
with self._state_lock:
|
|
@@ -307,14 +1137,100 @@ class StreamingGateway:
|
|
|
307
1137
|
|
|
308
1138
|
stats["is_streaming"] = self.is_streaming
|
|
309
1139
|
stats["my_stream_keys"] = list(self._my_stream_keys)
|
|
1140
|
+
stats["stream_key_to_camera_id"] = self._stream_key_to_camera_id.copy()
|
|
310
1141
|
stats["event_listening_enabled"] = self.enable_event_listening
|
|
1142
|
+
stats["use_async_workers"] = self.use_async_workers
|
|
1143
|
+
stats["use_gstreamer"] = self.use_gstreamer
|
|
1144
|
+
stats["use_ffmpeg"] = self.use_ffmpeg
|
|
1145
|
+
stats["use_nvdec"] = self.use_nvdec
|
|
311
1146
|
|
|
312
|
-
# Add
|
|
313
|
-
if self.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
1147
|
+
# Add backend-specific statistics
|
|
1148
|
+
if self.use_nvdec:
|
|
1149
|
+
# NVDEC statistics
|
|
1150
|
+
stats["nvdec_config"] = {
|
|
1151
|
+
"gpu_id": self.nvdec_gpu_id,
|
|
1152
|
+
"num_gpus": self.nvdec_num_gpus,
|
|
1153
|
+
"pool_size": self.nvdec_pool_size,
|
|
1154
|
+
"burst_size": self.nvdec_burst_size,
|
|
1155
|
+
"frame_width": self.nvdec_frame_width,
|
|
1156
|
+
"frame_height": self.nvdec_frame_height,
|
|
1157
|
+
"num_slots": self.nvdec_num_slots,
|
|
1158
|
+
"target_fps": self.nvdec_target_fps,
|
|
1159
|
+
}
|
|
1160
|
+
if self.nvdec_worker_manager:
|
|
1161
|
+
try:
|
|
1162
|
+
stats["worker_stats"] = self.nvdec_worker_manager.get_worker_statistics()
|
|
1163
|
+
stats["camera_assignments"] = self.nvdec_worker_manager.get_camera_assignments()
|
|
1164
|
+
except Exception as exc:
|
|
1165
|
+
logging.warning(f"Failed to get NVDEC worker stats: {exc}")
|
|
1166
|
+
elif self.use_ffmpeg:
|
|
1167
|
+
# FFmpeg statistics
|
|
1168
|
+
stats["ffmpeg_config"] = {
|
|
1169
|
+
"hwaccel": self.ffmpeg_hwaccel,
|
|
1170
|
+
"threads": self.ffmpeg_threads,
|
|
1171
|
+
"low_latency": self.ffmpeg_low_latency,
|
|
1172
|
+
"pixel_format": self.ffmpeg_pixel_format,
|
|
1173
|
+
}
|
|
1174
|
+
if self.use_async_workers:
|
|
1175
|
+
# FFmpegWorkerManager statistics
|
|
1176
|
+
if self.ffmpeg_worker_manager:
|
|
1177
|
+
try:
|
|
1178
|
+
stats["worker_stats"] = self.ffmpeg_worker_manager.get_worker_statistics()
|
|
1179
|
+
stats["camera_assignments"] = self.ffmpeg_worker_manager.get_camera_assignments()
|
|
1180
|
+
except Exception as exc:
|
|
1181
|
+
logging.warning(f"Failed to get FFmpeg worker stats: {exc}")
|
|
1182
|
+
else:
|
|
1183
|
+
# FFmpegCameraStreamer statistics
|
|
1184
|
+
if self.ffmpeg_streamer:
|
|
1185
|
+
try:
|
|
1186
|
+
stats["transmission_stats"] = self.ffmpeg_streamer.get_transmission_stats()
|
|
1187
|
+
except Exception as exc:
|
|
1188
|
+
logging.warning(f"Failed to get FFmpeg transmission stats: {exc}")
|
|
1189
|
+
elif self.use_gstreamer:
|
|
1190
|
+
# GStreamer statistics
|
|
1191
|
+
stats["gstreamer_config"] = {
|
|
1192
|
+
"encoder": self.gstreamer_encoder,
|
|
1193
|
+
"codec": self.gstreamer_codec,
|
|
1194
|
+
"preset": self.gstreamer_preset,
|
|
1195
|
+
"gpu_id": self.gstreamer_gpu_id,
|
|
1196
|
+
"platform": self.gstreamer_platform,
|
|
1197
|
+
"use_hardware_decode": self.gstreamer_use_hardware_decode,
|
|
1198
|
+
"use_hardware_jpeg": self.gstreamer_use_hardware_jpeg,
|
|
1199
|
+
"jetson_use_nvmm": self.gstreamer_jetson_use_nvmm,
|
|
1200
|
+
"frame_optimizer_mode": self.gstreamer_frame_optimizer_mode,
|
|
1201
|
+
"fallback_on_error": self.gstreamer_fallback_on_error,
|
|
1202
|
+
"verbose_logging": self.gstreamer_verbose_logging,
|
|
1203
|
+
}
|
|
1204
|
+
if self.use_async_workers:
|
|
1205
|
+
# GStreamerWorkerManager statistics
|
|
1206
|
+
if self.gstreamer_worker_manager:
|
|
1207
|
+
try:
|
|
1208
|
+
stats["worker_stats"] = self.gstreamer_worker_manager.get_worker_statistics()
|
|
1209
|
+
stats["camera_assignments"] = self.gstreamer_worker_manager.get_camera_assignments()
|
|
1210
|
+
except Exception as exc:
|
|
1211
|
+
logging.warning(f"Failed to get GStreamer worker stats: {exc}")
|
|
1212
|
+
else:
|
|
1213
|
+
# GStreamerCameraStreamer statistics
|
|
1214
|
+
if self.gstreamer_streamer:
|
|
1215
|
+
try:
|
|
1216
|
+
stats["transmission_stats"] = self.gstreamer_streamer.get_transmission_stats()
|
|
1217
|
+
except Exception as exc:
|
|
1218
|
+
logging.warning(f"Failed to get GStreamer transmission stats: {exc}")
|
|
1219
|
+
elif self.use_async_workers:
|
|
1220
|
+
# WorkerManager statistics
|
|
1221
|
+
if self.worker_manager:
|
|
1222
|
+
try:
|
|
1223
|
+
stats["worker_stats"] = self.worker_manager.get_worker_statistics()
|
|
1224
|
+
stats["camera_assignments"] = self.worker_manager.get_camera_assignments()
|
|
1225
|
+
except Exception as exc:
|
|
1226
|
+
logging.warning(f"Failed to get worker manager stats: {exc}")
|
|
1227
|
+
else:
|
|
1228
|
+
# CameraStreamer statistics
|
|
1229
|
+
if self.camera_streamer:
|
|
1230
|
+
try:
|
|
1231
|
+
stats["transmission_stats"] = self.camera_streamer.get_transmission_stats()
|
|
1232
|
+
except Exception as exc:
|
|
1233
|
+
logging.warning(f"Failed to get transmission stats: {exc}")
|
|
318
1234
|
|
|
319
1235
|
# Add camera manager statistics
|
|
320
1236
|
if self.camera_manager:
|
|
@@ -353,6 +1269,38 @@ class StreamingGateway:
|
|
|
353
1269
|
"streaming_gateway_id": self.streaming_gateway_id,
|
|
354
1270
|
"inputs_config": inputs_config_dict,
|
|
355
1271
|
"force_restart": self.force_restart,
|
|
1272
|
+
"use_async_workers": self.use_async_workers,
|
|
1273
|
+
"num_workers": self.num_workers,
|
|
1274
|
+
"max_cameras_per_worker": self.max_cameras_per_worker,
|
|
1275
|
+
# FFmpeg configuration
|
|
1276
|
+
"use_ffmpeg": self.use_ffmpeg,
|
|
1277
|
+
"ffmpeg_hwaccel": self.ffmpeg_hwaccel,
|
|
1278
|
+
"ffmpeg_threads": self.ffmpeg_threads,
|
|
1279
|
+
"ffmpeg_low_latency": self.ffmpeg_low_latency,
|
|
1280
|
+
"ffmpeg_pixel_format": self.ffmpeg_pixel_format,
|
|
1281
|
+
# GStreamer configuration
|
|
1282
|
+
"use_gstreamer": self.use_gstreamer,
|
|
1283
|
+
"gstreamer_encoder": self.gstreamer_encoder,
|
|
1284
|
+
"gstreamer_codec": self.gstreamer_codec,
|
|
1285
|
+
"gstreamer_preset": self.gstreamer_preset,
|
|
1286
|
+
"gstreamer_gpu_id": self.gstreamer_gpu_id,
|
|
1287
|
+
"gstreamer_platform": self.gstreamer_platform,
|
|
1288
|
+
"gstreamer_use_hardware_decode": self.gstreamer_use_hardware_decode,
|
|
1289
|
+
"gstreamer_use_hardware_jpeg": self.gstreamer_use_hardware_jpeg,
|
|
1290
|
+
"gstreamer_jetson_use_nvmm": self.gstreamer_jetson_use_nvmm,
|
|
1291
|
+
"gstreamer_frame_optimizer_mode": self.gstreamer_frame_optimizer_mode,
|
|
1292
|
+
"gstreamer_fallback_on_error": self.gstreamer_fallback_on_error,
|
|
1293
|
+
"gstreamer_verbose_logging": self.gstreamer_verbose_logging,
|
|
1294
|
+
# NVDEC configuration
|
|
1295
|
+
"use_nvdec": self.use_nvdec,
|
|
1296
|
+
"nvdec_gpu_id": self.nvdec_gpu_id,
|
|
1297
|
+
"nvdec_num_gpus": self.nvdec_num_gpus,
|
|
1298
|
+
"nvdec_pool_size": self.nvdec_pool_size,
|
|
1299
|
+
"nvdec_burst_size": self.nvdec_burst_size,
|
|
1300
|
+
"nvdec_frame_width": self.nvdec_frame_width,
|
|
1301
|
+
"nvdec_frame_height": self.nvdec_frame_height,
|
|
1302
|
+
"nvdec_num_slots": self.nvdec_num_slots,
|
|
1303
|
+
"nvdec_target_fps": self.nvdec_target_fps,
|
|
356
1304
|
}
|
|
357
1305
|
|
|
358
1306
|
def _emergency_cleanup(self):
|