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
@@ -0,0 +1,680 @@
1
+ """Platform-specific GStreamer pipeline builders.
2
+
3
+ This module implements the Strategy pattern for constructing optimized GStreamer pipelines
4
+ based on detected hardware platform (Jetson, Desktop NVIDIA GPU, Intel/AMD GPU, CPU-only).
5
+
6
+ Each builder encapsulates platform-specific optimizations:
7
+ - Jetson: nvjpegenc, nvvidconv, NVMM memory, nvv4l2decoder
8
+ - Desktop NVIDIA GPU: nvdec decode, nvh264enc encode, CPU jpegenc (no HW JPEG)
9
+ - Intel/AMD GPU: VAAPI (vaapijpegenc, vaapih264dec)
10
+ - CPU-only: Software fallback (x264enc, jpegenc, avdec_h264)
11
+ """
12
+ import logging
13
+ from abc import ABC, abstractmethod
14
+ from typing import Dict, Tuple, Optional
15
+
16
+ from .device_detection import PlatformInfo, PlatformType
17
+ from .gstreamer_camera_streamer import GStreamerConfig
18
+
19
+
20
+ class PipelineBuilder(ABC):
21
+ """Abstract base class for platform-specific pipeline builders.
22
+
23
+ Implements Template Method pattern with platform-specific overrides.
24
+ """
25
+
26
+ def __init__(self, config: GStreamerConfig, platform_info: PlatformInfo):
27
+ """Initialize pipeline builder.
28
+
29
+ Args:
30
+ config: GStreamer configuration
31
+ platform_info: Detected platform information
32
+ """
33
+ self.config = config
34
+ self.platform_info = platform_info
35
+ self.logger = logging.getLogger(f"{__name__}.{self.__class__.__name__}")
36
+
37
+ @abstractmethod
38
+ def build_decode_element(self, source_type: str, source: str) -> str:
39
+ """Build hardware-accelerated decode element for source.
40
+
41
+ Args:
42
+ source_type: Type of source (rtsp, file, camera)
43
+ source: Source URI or path
44
+
45
+ Returns:
46
+ GStreamer decode element string
47
+ """
48
+ pass
49
+
50
+ @abstractmethod
51
+ def build_convert_element(self) -> str:
52
+ """Build color conversion element (CPU or GPU accelerated).
53
+
54
+ Returns:
55
+ GStreamer converter element string
56
+ """
57
+ pass
58
+
59
+ @abstractmethod
60
+ def build_encode_element(self, encoder: str, quality: int, bitrate: int) -> Tuple[str, str]:
61
+ """Build encoder element and output caps.
62
+
63
+ Args:
64
+ encoder: Encoder type (jpeg, h264, h265)
65
+ quality: Quality setting (JPEG quality or video quality hint)
66
+ bitrate: Bitrate for video encoders (bps)
67
+
68
+ Returns:
69
+ Tuple of (encoder_string, output_caps)
70
+ """
71
+ pass
72
+
73
+ @abstractmethod
74
+ def build_memory_element(self, encoder: str) -> str:
75
+ """Build memory transfer/format element (NVMM, CUDA, standard).
76
+
77
+ Args:
78
+ encoder: Encoder type being used
79
+
80
+ Returns:
81
+ GStreamer memory/format caps string
82
+ """
83
+ pass
84
+
85
+ def build_complete_pipeline(self, builder_config: Dict) -> str:
86
+ """Build complete GStreamer pipeline (Template Method).
87
+
88
+ Args:
89
+ builder_config: Pipeline configuration dict with:
90
+ - source_type: str
91
+ - source: str
92
+ - width: int
93
+ - height: int
94
+ - fps: int
95
+ - encoder: str
96
+ - quality: int
97
+ - bitrate: int
98
+
99
+ Returns:
100
+ Complete GStreamer pipeline string
101
+ """
102
+ source_type = builder_config['source_type']
103
+ source = builder_config['source']
104
+ width = builder_config['width']
105
+ height = builder_config['height']
106
+ fps = builder_config['fps']
107
+ encoder = builder_config['encoder']
108
+ quality = builder_config['quality']
109
+ bitrate = builder_config['bitrate']
110
+
111
+ # Build pipeline elements
112
+ decode = self.build_decode_element(source_type, source)
113
+ convert = self.build_convert_element()
114
+ memory_caps = self.build_memory_element(encoder)
115
+ encode_elem, caps_out = self.build_encode_element(encoder, quality, bitrate)
116
+
117
+ # Assemble pipeline
118
+ pipeline = self._assemble_pipeline(
119
+ decode, convert, memory_caps, encode_elem, caps_out,
120
+ width, height, fps
121
+ )
122
+
123
+ if self.config.verbose_pipeline_logging:
124
+ self.logger.info(f"Pipeline: {pipeline}")
125
+
126
+ return pipeline
127
+
128
+ def _assemble_pipeline(
129
+ self,
130
+ decode: str,
131
+ convert: str,
132
+ memory_caps: str,
133
+ encode: str,
134
+ caps_out: str,
135
+ width: int,
136
+ height: int,
137
+ fps: int
138
+ ) -> str:
139
+ """Assemble complete pipeline from components.
140
+
141
+ Args:
142
+ decode: Decode element string
143
+ convert: Convert element string
144
+ memory_caps: Memory/format caps string
145
+ encode: Encode element string
146
+ caps_out: Output caps string
147
+ width: Target width
148
+ height: Target height
149
+ fps: Target FPS
150
+
151
+ Returns:
152
+ Complete pipeline string
153
+ """
154
+ # Build pipeline with appropriate queue settings
155
+ # Use smaller buffer for hardware encoders (nvh264enc, nvh265enc, nvjpegenc, nvv4l2h264enc, etc.)
156
+ is_hw_encoder = any(hw in encode for hw in ['nvh264enc', 'nvh265enc', 'nvjpegenc', 'nvv4l2', 'vaapi'])
157
+ buffer_size = 1 if is_hw_encoder else 2
158
+
159
+ if memory_caps:
160
+ # Hardware-accelerated pipeline with specific memory format
161
+ pipeline = (
162
+ f"{decode} ! "
163
+ f"{convert} ! "
164
+ f"video/x-raw,width={width},height={height} ! "
165
+ f"{memory_caps} ! "
166
+ f"videorate ! video/x-raw,framerate={fps}/1 ! "
167
+ f"queue max-size-buffers={buffer_size} leaky=downstream ! "
168
+ f"{encode} ! {caps_out} ! "
169
+ f"queue max-size-buffers={buffer_size} leaky=downstream ! "
170
+ f"appsink name=sink sync=false async=false "
171
+ f"max-buffers={buffer_size} drop=true enable-last-sample=false"
172
+ )
173
+ else:
174
+ # Standard pipeline
175
+ pipeline = (
176
+ f"{decode} ! "
177
+ f"{convert} ! "
178
+ f"videoscale ! "
179
+ f"video/x-raw,width={width},height={height} ! "
180
+ f"videorate ! video/x-raw,framerate={fps}/1 ! "
181
+ f"queue max-size-buffers={buffer_size} leaky=downstream ! "
182
+ f"{encode} ! {caps_out} ! "
183
+ f"queue max-size-buffers={buffer_size} leaky=downstream ! "
184
+ f"appsink name=sink sync=false async=false "
185
+ f"max-buffers={buffer_size} drop=true enable-last-sample=false"
186
+ )
187
+
188
+ return pipeline
189
+
190
+ def build_dual_appsink_pipeline(self, builder_config: Dict) -> str:
191
+ """Build pipeline with dual appsinks for FrameOptimizer.
192
+
193
+ Args:
194
+ builder_config: Pipeline configuration dict
195
+
196
+ Returns:
197
+ Pipeline string with similarity-sink and output-sink
198
+ """
199
+ source_type = builder_config['source_type']
200
+ source = builder_config['source']
201
+ width = builder_config['width']
202
+ height = builder_config['height']
203
+ fps = builder_config['fps']
204
+ encoder = builder_config['encoder']
205
+ quality = builder_config['quality']
206
+ bitrate = builder_config['bitrate']
207
+
208
+ # Build pipeline elements
209
+ decode = self.build_decode_element(source_type, source)
210
+ convert = self.build_convert_element()
211
+ memory_caps = self.build_memory_element(encoder)
212
+ encode_elem, caps_out = self.build_encode_element(encoder, quality, bitrate)
213
+
214
+ # Similarity detection: downscale to 160x120 for performance (16x fewer pixels)
215
+ similarity_width = 160
216
+ similarity_height = 120
217
+
218
+ # Build dual-sink pipeline with tee
219
+ pipeline = (
220
+ f"{decode} ! "
221
+ f"{convert} ! "
222
+ f"video/x-raw,width={width},height={height} ! "
223
+ f"videorate ! video/x-raw,framerate={fps}/1 ! "
224
+ f"tee name=t "
225
+ # Branch 1: Similarity detection (downscaled, raw frames)
226
+ f"t. ! queue max-size-buffers=1 leaky=downstream ! "
227
+ f"videoscale ! video/x-raw,width={similarity_width},height={similarity_height},format=RGB ! "
228
+ f"appsink name=similarity-sink sync=false async=false max-buffers=1 drop=true enable-last-sample=false "
229
+ # Branch 2: Full encode for output
230
+ f"t. ! queue max-size-buffers=1 leaky=downstream ! "
231
+ )
232
+
233
+ # Add memory caps if needed (for hardware encoding)
234
+ if memory_caps:
235
+ pipeline += f"{memory_caps} ! "
236
+
237
+ pipeline += (
238
+ f"{encode_elem} ! {caps_out} ! "
239
+ f"appsink name=output-sink sync=false async=false max-buffers=1 drop=true enable-last-sample=false"
240
+ )
241
+
242
+ if self.config.verbose_pipeline_logging:
243
+ self.logger.info(f"Dual-appsink pipeline: {pipeline}")
244
+
245
+ return pipeline
246
+
247
+
248
+ class JetsonPipelineBuilder(PipelineBuilder):
249
+ """Jetson-optimized pipeline builder.
250
+
251
+ Optimizations:
252
+ - nvjpegenc for hardware JPEG encoding (10x faster than CPU)
253
+ - nvvidconv for GPU-accelerated color conversion
254
+ - NVMM zero-copy memory
255
+ - nvv4l2decoder for hardware video decode
256
+ - nvv4l2h264enc/h265enc for hardware video encode
257
+ """
258
+
259
+ def build_decode_element(self, source_type: str, source: str) -> str:
260
+ if not self.config.use_hardware_decode:
261
+ return self._build_cpu_decode(source_type, source)
262
+
263
+ if source_type == "rtsp":
264
+ # RTSP with hardware decode
265
+ return (
266
+ f"rtspsrc location={source} latency=50 buffer-mode=auto ! "
267
+ f"rtph264depay ! h264parse ! "
268
+ f"nvv4l2decoder enable-max-performance=1"
269
+ )
270
+ elif source_type == "file":
271
+ # Video file with hardware decode
272
+ ext = source.lower().split('.')[-1] if '.' in source else ''
273
+ if ext in ('mp4', 'mov', 'm4v'):
274
+ return (
275
+ f"filesrc location={source} ! qtdemux ! h264parse ! "
276
+ f"nvv4l2decoder"
277
+ )
278
+ elif ext in ('mkv', 'webm'):
279
+ return f"filesrc location={source} ! matroskademux ! h264parse ! nvv4l2decoder"
280
+ elif ext == 'avi':
281
+ return f"filesrc location={source} ! avidemux ! h264parse ! nvv4l2decoder"
282
+ else:
283
+ # Unknown format, try qtdemux
284
+ return f"filesrc location={source} ! qtdemux ! h264parse ! nvv4l2decoder"
285
+
286
+ # Fallback
287
+ return self._build_cpu_decode(source_type, source)
288
+
289
+ def _build_cpu_decode(self, source_type: str, source: str) -> str:
290
+ """Fallback CPU decode."""
291
+ if source_type == "rtsp":
292
+ return (
293
+ f"rtspsrc location={source} latency=100 ! "
294
+ f"rtph264depay ! h264parse ! avdec_h264"
295
+ )
296
+ elif source_type == "file":
297
+ ext = source.lower().split('.')[-1] if '.' in source else ''
298
+ if ext in ('mp4', 'mov'):
299
+ return f"filesrc location={source} ! qtdemux ! avdec_h264"
300
+ elif ext in ('mkv', 'webm'):
301
+ return f"filesrc location={source} ! matroskademux ! avdec_h264"
302
+ else:
303
+ return f"filesrc location={source} ! qtdemux ! avdec_h264"
304
+
305
+ return "videotestsrc pattern=smpte is-live=true"
306
+
307
+ def build_convert_element(self) -> str:
308
+ # Use nvvidconv (GPU-accelerated) instead of videoconvert
309
+ if 'nvvidconv' in self.platform_info.available_converters:
310
+ return "nvvidconv"
311
+ return "videoconvert"
312
+
313
+ def build_encode_element(self, encoder: str, quality: int, bitrate: int) -> Tuple[str, str]:
314
+ if encoder == "jpeg":
315
+ # Hardware JPEG encoding - HUGE performance improvement
316
+ if 'nvjpegenc' in self.platform_info.available_encoders and self.config.use_hardware_jpeg:
317
+ return (
318
+ f"nvjpegenc quality={quality}",
319
+ "image/jpeg"
320
+ )
321
+ # Fallback to CPU JPEG
322
+ return (f"jpegenc quality={quality} idct-method=ifast", "image/jpeg")
323
+
324
+ elif encoder == "h264":
325
+ # Hardware H.264 encoding
326
+ if 'nvv4l2h264enc' in self.platform_info.available_encoders:
327
+ return (
328
+ f"nvv4l2h264enc preset-level=1 bitrate={bitrate} "
329
+ f"iframeinterval={self.config.gop_size} insert-sps-pps=true",
330
+ "video/x-h264,profile=main"
331
+ )
332
+ elif 'nvh264enc' in self.platform_info.available_encoders:
333
+ return (
334
+ f"nvh264enc preset={self.config.preset} bitrate={bitrate//1000} "
335
+ f"gop-size={self.config.gop_size} zerolatency=true bframes=0",
336
+ "video/x-h264,profile=main"
337
+ )
338
+ # Fallback to x264
339
+ return (
340
+ f"x264enc speed-preset=ultrafast tune=zerolatency bitrate={bitrate//1000} threads=2",
341
+ "video/x-h264,profile=baseline"
342
+ )
343
+
344
+ elif encoder == "h265":
345
+ # Hardware H.265 encoding
346
+ if 'nvv4l2h265enc' in self.platform_info.available_encoders:
347
+ return (
348
+ f"nvv4l2h265enc preset-level=1 bitrate={bitrate} "
349
+ f"iframeinterval={self.config.gop_size}",
350
+ "video/x-h265,profile=main"
351
+ )
352
+ elif 'nvh265enc' in self.platform_info.available_encoders:
353
+ return (
354
+ f"nvh265enc preset={self.config.preset} bitrate={bitrate//1000} "
355
+ f"gop-size={self.config.gop_size}",
356
+ "video/x-h265,profile=main"
357
+ )
358
+
359
+ # Unknown encoder
360
+ raise ValueError(f"Unsupported encoder: {encoder}")
361
+
362
+ def build_memory_element(self, encoder: str) -> str:
363
+ # NVMM zero-copy memory for Jetson
364
+ if self.config.jetson_use_nvmm and self.platform_info.supports_nvmm:
365
+ if encoder == "jpeg" and 'nvjpegenc' in self.platform_info.available_encoders:
366
+ return "video/x-raw(memory:NVMM),format=NV12"
367
+ elif encoder in ("h264", "h265"):
368
+ return "video/x-raw(memory:NVMM),format=NV12"
369
+ return ""
370
+
371
+
372
+ class DesktopNvidiaGpuPipelineBuilder(PipelineBuilder):
373
+ """Desktop NVIDIA GPU pipeline builder.
374
+
375
+ Optimizations:
376
+ - nvdec for hardware video decode
377
+ - nvh264enc/nvh265enc for hardware video encode
378
+ - CUDA memory for zero-copy (video codecs only)
379
+
380
+ Limitations:
381
+ - NO hardware JPEG encoding (GStreamer limitation on desktop GPUs)
382
+ - Must use CPU jpegenc
383
+ """
384
+
385
+ def build_decode_element(self, source_type: str, source: str) -> str:
386
+ if not self.config.use_hardware_decode:
387
+ return self._build_cpu_decode(source_type, source)
388
+
389
+ if 'nvdec' not in self.platform_info.available_decoders:
390
+ return self._build_cpu_decode(source_type, source)
391
+
392
+ if source_type == "rtsp":
393
+ return (
394
+ f"rtspsrc location={source} latency=100 buffer-mode=auto ! "
395
+ f"rtph264depay ! h264parse ! "
396
+ f"nvdec"
397
+ )
398
+ elif source_type == "file":
399
+ ext = source.lower().split('.')[-1] if '.' in source else ''
400
+ if ext in ('mp4', 'mov', 'm4v'):
401
+ return f"filesrc location={source} ! qtdemux ! h264parse ! nvdec"
402
+ elif ext in ('mkv', 'webm'):
403
+ return f"filesrc location={source} ! matroskademux ! h264parse ! nvdec"
404
+ elif ext == 'avi':
405
+ return f"filesrc location={source} ! avidemux ! h264parse ! nvdec"
406
+ else:
407
+ return f"filesrc location={source} ! qtdemux ! h264parse ! nvdec"
408
+
409
+ return self._build_cpu_decode(source_type, source)
410
+
411
+ def _build_cpu_decode(self, source_type: str, source: str) -> str:
412
+ if source_type == "rtsp":
413
+ return f"rtspsrc location={source} latency=100 ! rtph264depay ! h264parse ! avdec_h264"
414
+ elif source_type == "file":
415
+ ext = source.lower().split('.')[-1] if '.' in source else ''
416
+ if ext in ('mp4', 'mov'):
417
+ return f"filesrc location={source} ! qtdemux ! avdec_h264"
418
+ elif ext in ('mkv', 'webm'):
419
+ return f"filesrc location={source} ! matroskademux ! avdec_h264"
420
+ else:
421
+ return f"filesrc location={source} ! qtdemux ! avdec_h264"
422
+ return "videotestsrc pattern=smpte is-live=true"
423
+
424
+ def build_convert_element(self) -> str:
425
+ # Use standard videoconvert (GPU conversion for CUDA memory handled separately)
426
+ return "videoconvert"
427
+
428
+ def build_encode_element(self, encoder: str, quality: int, bitrate: int) -> Tuple[str, str]:
429
+ if encoder == "jpeg":
430
+ # LIMITATION: No hardware JPEG on desktop GPUs in GStreamer
431
+ # Must use CPU jpegenc
432
+ self.logger.debug("Using CPU JPEG encoding (no hardware support on desktop GPU)")
433
+ return (
434
+ f"jpegenc quality={quality} idct-method=ifast",
435
+ "image/jpeg"
436
+ )
437
+
438
+ elif encoder == "h264":
439
+ # Hardware H.264 encoding with NVENC
440
+ if 'nvh264enc' in self.platform_info.available_encoders:
441
+ return (
442
+ f"nvh264enc cuda-device-id={self.config.gpu_id} "
443
+ f"preset={self.config.preset} bitrate={bitrate//1000} "
444
+ f"gop-size={self.config.gop_size} zerolatency=true "
445
+ f"rc-lookahead=0 bframes=0 rc-mode=cbr-ld-hq",
446
+ "video/x-h264,profile=main"
447
+ )
448
+ # Fallback to x264
449
+ return (
450
+ f"x264enc speed-preset=ultrafast tune=zerolatency bitrate={bitrate//1000} threads=4",
451
+ "video/x-h264,profile=baseline"
452
+ )
453
+
454
+ elif encoder == "h265":
455
+ # Hardware H.265 encoding with NVENC
456
+ if 'nvh265enc' in self.platform_info.available_encoders:
457
+ return (
458
+ f"nvh265enc cuda-device-id={self.config.gpu_id} "
459
+ f"preset={self.config.preset} bitrate={bitrate//1000} "
460
+ f"gop-size={self.config.gop_size} zerolatency=true "
461
+ f"rc-lookahead=0 bframes=0",
462
+ "video/x-h265,profile=main"
463
+ )
464
+
465
+ raise ValueError(f"Unsupported encoder: {encoder}")
466
+
467
+ def build_memory_element(self, encoder: str) -> str:
468
+ # CUDA memory for video codecs (if enabled and using NVENC)
469
+ if encoder in ("h264", "h265") and self.config.use_cuda_memory:
470
+ if f"nv{encoder}enc" in self.platform_info.available_encoders:
471
+ return f"cudaupload cuda-device-id={self.config.gpu_id} ! video/x-raw(memory:CUDAMemory),format=NV12"
472
+ return ""
473
+
474
+
475
+ class IntelGpuPipelineBuilder(PipelineBuilder):
476
+ """Intel GPU pipeline builder with VAAPI.
477
+
478
+ Optimizations:
479
+ - vaapijpegenc for hardware JPEG encoding
480
+ - vaapih264enc/h265enc for hardware video encode
481
+ - vaapih264dec for hardware video decode
482
+ """
483
+
484
+ def build_decode_element(self, source_type: str, source: str) -> str:
485
+ if not self.config.use_hardware_decode or 'vaapih264dec' not in self.platform_info.available_decoders:
486
+ return self._build_cpu_decode(source_type, source)
487
+
488
+ if source_type == "rtsp":
489
+ return (
490
+ f"rtspsrc location={source} latency=100 ! "
491
+ f"rtph264depay ! h264parse ! "
492
+ f"vaapih264dec"
493
+ )
494
+ elif source_type == "file":
495
+ ext = source.lower().split('.')[-1] if '.' in source else ''
496
+ if ext in ('mp4', 'mov', 'm4v'):
497
+ return f"filesrc location={source} ! qtdemux ! h264parse ! vaapih264dec"
498
+ elif ext in ('mkv', 'webm'):
499
+ return f"filesrc location={source} ! matroskademux ! h264parse ! vaapih264dec"
500
+ else:
501
+ return f"filesrc location={source} ! qtdemux ! h264parse ! vaapih264dec"
502
+
503
+ return self._build_cpu_decode(source_type, source)
504
+
505
+ def _build_cpu_decode(self, source_type: str, source: str) -> str:
506
+ if source_type == "rtsp":
507
+ return f"rtspsrc location={source} latency=100 ! rtph264depay ! h264parse ! avdec_h264"
508
+ elif source_type == "file":
509
+ ext = source.lower().split('.')[-1] if '.' in source else ''
510
+ if ext in ('mp4', 'mov'):
511
+ return f"filesrc location={source} ! qtdemux ! avdec_h264"
512
+ else:
513
+ return f"filesrc location={source} ! matroskademux ! avdec_h264"
514
+ return "videotestsrc pattern=smpte is-live=true"
515
+
516
+ def build_convert_element(self) -> str:
517
+ return "videoconvert"
518
+
519
+ def build_encode_element(self, encoder: str, quality: int, bitrate: int) -> Tuple[str, str]:
520
+ if encoder == "jpeg":
521
+ # Hardware JPEG encoding via VAAPI
522
+ if 'vaapijpegenc' in self.platform_info.available_encoders and self.config.use_hardware_jpeg:
523
+ return (
524
+ f"vaapijpegenc quality={quality}",
525
+ "image/jpeg"
526
+ )
527
+ # Fallback to CPU
528
+ return (f"jpegenc quality={quality} idct-method=ifast", "image/jpeg")
529
+
530
+ elif encoder == "h264":
531
+ # Hardware H.264 encoding via VAAPI
532
+ if 'vaapih264enc' in self.platform_info.available_encoders:
533
+ return (
534
+ f"vaapih264enc bitrate={bitrate//1000} rate-control=cbr keyframe-period={self.config.gop_size}",
535
+ "video/x-h264,profile=main"
536
+ )
537
+ # Fallback to x264
538
+ return (
539
+ f"x264enc speed-preset=ultrafast tune=zerolatency bitrate={bitrate//1000} threads=4",
540
+ "video/x-h264,profile=baseline"
541
+ )
542
+
543
+ elif encoder == "h265":
544
+ # Hardware H.265 encoding via VAAPI
545
+ if 'vaapih265enc' in self.platform_info.available_encoders:
546
+ return (
547
+ f"vaapih265enc bitrate={bitrate//1000} rate-control=cbr",
548
+ "video/x-h265,profile=main"
549
+ )
550
+
551
+ raise ValueError(f"Unsupported encoder: {encoder}")
552
+
553
+ def build_memory_element(self, encoder: str) -> str:
554
+ # VAAPI uses its own internal memory management
555
+ return ""
556
+
557
+
558
+ class AmdGpuPipelineBuilder(IntelGpuPipelineBuilder):
559
+ """AMD GPU pipeline builder with VAAPI.
560
+
561
+ Inherits from IntelGpuPipelineBuilder as AMD uses same VAAPI interface.
562
+ Can be extended in future for AMD-specific optimizations.
563
+ """
564
+ pass
565
+
566
+
567
+ class CpuOnlyPipelineBuilder(PipelineBuilder):
568
+ """CPU-only fallback pipeline builder.
569
+
570
+ Uses software encoders/decoders:
571
+ - jpegenc for JPEG
572
+ - x264enc for H.264
573
+ - avdec_h264 for decode
574
+ """
575
+
576
+ def build_decode_element(self, source_type: str, source: str) -> str:
577
+ if source_type == "rtsp":
578
+ return (
579
+ f"rtspsrc location={source} latency=100 buffer-mode=auto ! "
580
+ f"rtph264depay ! h264parse ! "
581
+ f"avdec_h264"
582
+ )
583
+ elif source_type == "file":
584
+ ext = source.lower().split('.')[-1] if '.' in source else ''
585
+ if ext in ('mp4', 'mov', 'm4v'):
586
+ return f"filesrc location={source} ! qtdemux ! avdec_h264"
587
+ elif ext in ('mkv', 'webm'):
588
+ return f"filesrc location={source} ! matroskademux ! avdec_h264"
589
+ elif ext == 'avi':
590
+ return f"filesrc location={source} ! avidemux ! avdec_h264"
591
+ else:
592
+ return f"filesrc location={source} ! qtdemux ! avdec_h264"
593
+
594
+ return "videotestsrc pattern=smpte is-live=true"
595
+
596
+ def build_convert_element(self) -> str:
597
+ return "videoconvert"
598
+
599
+ def build_encode_element(self, encoder: str, quality: int, bitrate: int) -> Tuple[str, str]:
600
+ if encoder == "jpeg":
601
+ return (f"jpegenc quality={quality} idct-method=ifast", "image/jpeg")
602
+
603
+ elif encoder == "h264":
604
+ # Software H.264 encoding with x264
605
+ threads = min(4, self.platform_info.gpu_count or 4)
606
+ return (
607
+ f"x264enc speed-preset=ultrafast tune=zerolatency "
608
+ f"bitrate={bitrate//1000} key-int-max={self.config.gop_size} "
609
+ f"bframes=0 threads={threads} sliced-threads=true",
610
+ "video/x-h264,profile=baseline"
611
+ )
612
+
613
+ elif encoder == "h265":
614
+ # Software H.265 encoding (if available)
615
+ if 'x265enc' in self.platform_info.available_encoders:
616
+ return (
617
+ f"x265enc speed-preset=ultrafast bitrate={bitrate//1000} "
618
+ f"key-int-max={self.config.gop_size}",
619
+ "video/x-h265,profile=main"
620
+ )
621
+
622
+ raise ValueError(f"Unsupported encoder: {encoder}")
623
+
624
+ def build_memory_element(self, encoder: str) -> str:
625
+ return ""
626
+
627
+
628
+ class PipelineFactory:
629
+ """Factory for selecting appropriate pipeline builder based on platform."""
630
+
631
+ @staticmethod
632
+ def get_builder(config: GStreamerConfig, platform_info: PlatformInfo) -> PipelineBuilder:
633
+ """Select and instantiate appropriate pipeline builder.
634
+
635
+ Args:
636
+ config: GStreamer configuration
637
+ platform_info: Detected platform information
638
+
639
+ Returns:
640
+ Platform-specific PipelineBuilder instance
641
+ """
642
+ logger = logging.getLogger(__name__)
643
+
644
+ # Manual override if specified (and override enabled)
645
+ if config.platform != "auto" and config.enable_platform_override:
646
+ platform_map = {
647
+ "jetson": JetsonPipelineBuilder,
648
+ "desktop-gpu": DesktopNvidiaGpuPipelineBuilder,
649
+ "intel": IntelGpuPipelineBuilder,
650
+ "amd": AmdGpuPipelineBuilder,
651
+ "cpu": CpuOnlyPipelineBuilder,
652
+ }
653
+
654
+ builder_class = platform_map.get(config.platform)
655
+ if builder_class:
656
+ logger.info(f"Using manual platform override: {config.platform}")
657
+ return builder_class(config, platform_info)
658
+ else:
659
+ logger.warning(f"Invalid platform override '{config.platform}', using auto-detect")
660
+
661
+ # Auto-detect based on platform_info
662
+ if platform_info.platform_type == PlatformType.JETSON:
663
+ logger.info("Selected JetsonPipelineBuilder")
664
+ return JetsonPipelineBuilder(config, platform_info)
665
+
666
+ elif platform_info.platform_type == PlatformType.DESKTOP_NVIDIA_GPU:
667
+ logger.info("Selected DesktopNvidiaGpuPipelineBuilder")
668
+ return DesktopNvidiaGpuPipelineBuilder(config, platform_info)
669
+
670
+ elif platform_info.platform_type == PlatformType.INTEL_GPU:
671
+ logger.info("Selected IntelGpuPipelineBuilder")
672
+ return IntelGpuPipelineBuilder(config, platform_info)
673
+
674
+ elif platform_info.platform_type == PlatformType.AMD_GPU:
675
+ logger.info("Selected AmdGpuPipelineBuilder")
676
+ return AmdGpuPipelineBuilder(config, platform_info)
677
+
678
+ else:
679
+ logger.info("Selected CpuOnlyPipelineBuilder (fallback)")
680
+ return CpuOnlyPipelineBuilder(config, platform_info)