Skip to content

Commit 0f1b8f5

Browse files
melgamal-axongithub-actions[bot]Auto-format Bot
authored
Add GStreamer backend support for RTSP streaming (#92)
* using gst backend for opencv * Update documentation * restore dockerfile * update with gstreamer backend comment * fix test and remove gst backend test script * update readme with instructions for gst backend usage * add sample script for testing gst backend * misc code cleanup * update sample script to accept protocol arg * allow using different protocols * seperate dockerfile for opencv+gst build * unit tests for mock testing * set protocol, tcp udp or tcp+udp * fix unit test * Update documentation * stop blocking when grab() takes longer than timeout * Automatically reformatting code with black and isort * bump version * use toml file for installing sharable dependencies * remove comments * clean up gst support section * fix comment * Update documentation * updates per PR comments * trigger CI on PR * trigger CI on various PR states * Update documentation * fix unit tests (vibe-kanban 5a852cc6) please check and fix FAILED test/test\_all\_grabber\_types\_generic.py::TestAllGrabberTypes::test\_create\_config ```javascript FAILED test/test_rtsp_backends.py::TestRTSPBackends::test_gstreamer_backend_with_max_fps ``` * try with push only again --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Auto-format Bot <autoformatbot@groundlight.ai>
1 parent d63670e commit 0f1b8f5

13 files changed

Lines changed: 1480 additions & 28 deletions

‎.dockerignore‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
# Virtual environments - don't copy local venv into container
2+
.venv
3+
venv
4+
env
5+
6+
# Python cache
7+
__pycache__
8+
*.py[cod]
9+
*$py.class
10+
*.so
11+
.Python
12+
build/
13+
develop-eggs/
14+
dist/
15+
downloads/
16+
eggs/
17+
.eggs/
18+
lib/
19+
lib64/
20+
parts/
21+
sdist/
22+
var/
23+
wheels/
24+
*.egg-info/
25+
.installed.cfg
26+
*.egg
27+
28+
# IDE and editor files
29+
.idea/
30+
.vscode/
31+
*.swp
32+
*.swo
33+
*~
34+
35+
# Git
36+
.git
37+
.gitignore
38+
39+
# Docker files (avoid recursive copies)
40+
docker/
41+
42+
# Test cache
43+
.pytest_cache/
44+
.coverage
45+
htmlcov/
46+
47+
# macOS
48+
.DS_Store
49+
50+
# Local development
51+
*.log
52+

‎.github/workflows/tests.yaml‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Run integration tests against the integ API endpoint
22
name: test integ
3-
on: [push]
3+
on:
4+
push:
45
jobs:
56
run-tests:
67
runs-on: ubuntu-latest

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,7 @@ dmypy.json
130130

131131
# Pyre type checker
132132
.pyre/
133+
134+
# Test output artifacts
135+
output/*.jpg
136+
output/*.png

‎README.md‎

Lines changed: 142 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,11 @@ FrameGrab also provides basic motion detection functionality. FrameGrab requires
1212
- [Table of Contents](#table-of-contents)
1313
- [Installation](#installation)
1414
- [Optional Dependencies](#optional-dependencies)
15+
- [GStreamer Support](#gstreamer-support)
1516
- [Usage](#usage)
1617
- [Command line interface (CLI)](#command-line-interface-cli)
1718
- [Frame Grabbing](#frame-grabbing)
19+
- [RTSP Backends](#rtsp-backends)
1820
- [Configurations](#configurations)
1921
- [Autodiscovery](#autodiscovery)
2022
- [RTSP Discovery](#rtsp-discovery)
@@ -57,6 +59,63 @@ To install all optional dependencies, run:
5759
pip install framegrab[all]
5860
```
5961

62+
### GStreamer Support
63+
64+
The RTSP input type supports two backends: **FFmpeg** (default) and **GStreamer**. The GStreamer backend provides lower latency and better real-time performance for RTSP streaming.
65+
66+
**Why GStreamer for RTSP?**
67+
- ✅ **Zero-buffering** with leaky queues for always-fresh frames
68+
- ✅ **Lower latency** (~60-80ms vs 200-500ms with FFmpeg)
69+
- ✅ **Better network handling** with configurable timeouts
70+
- ✅ **Frame rate limiting** at the pipeline level
71+
72+
By default, `pip install opencv-python` includes only FFmpeg support. To use GStreamer, you need OpenCV compiled with GStreamer libraries.
73+
74+
#### Option 1: Docker (Recommended)
75+
76+
Use the sample `Dockerfile.gstreamer` which includes GStreamer support:
77+
78+
```bash
79+
# Build the Docker image
80+
docker build -f docker/Dockerfile.gstreamer -t framegrab-gstreamer .
81+
82+
# Run tests
83+
docker run --rm -v $(pwd):/app framegrab-gstreamer \
84+
python3 -m pytest test/
85+
86+
# Run your script
87+
docker run --rm -v $(pwd):/app framegrab-gstreamer \
88+
python3 your_script.py
89+
```
90+
91+
#### Option 2: System OpenCV (Ubuntu/Debian)
92+
93+
```bash
94+
# Install system OpenCV with GStreamer support
95+
sudo apt-get update
96+
sudo apt-get install -y \
97+
python3-opencv \
98+
gstreamer1.0-plugins-base \
99+
gstreamer1.0-plugins-good \
100+
gstreamer1.0-libav
101+
102+
# Install framegrab
103+
pip install framegrab
104+
105+
# Remove pip's opencv-python - we use system OpenCV (has GStreamer support)
106+
pip3 uninstall -y opencv-python opencv-python-headless opencv-contrib-python 2>/dev/null || true
107+
108+
# Downgrade numpy to 1.x - system OpenCV was compiled with numpy 1.x
109+
pip3 install --no-cache-dir "numpy<2"
110+
```
111+
112+
#### Verifying GStreamer Support
113+
114+
To check if your OpenCV installation has GStreamer support:
115+
116+
```bash
117+
python3 -c "import cv2; print('GStreamer: YES' if 'GStreamer' in cv2.getBuildInformation() else 'GStreamer: NO')"
118+
```
60119

61120
## Usage
62121

@@ -177,6 +236,42 @@ with FrameGrabber.create_grabber_yaml(config) as grabber:
177236
frame = grabber.grab()
178237
```
179238

239+
#### RTSP Backends
240+
241+
For RTSP streams, FrameGrab supports two backends:
242+
243+
1. **FFmpeg backend** (default) - Works with standard `pip install opencv-python`
244+
2. **GStreamer backend** - Recommended for low-latency, real-time RTSP streaming (requires [GStreamer support](#gstreamer-support))
245+
246+
To use the GStreamer backend, set `backend="gstreamer"` in your config:
247+
248+
```python
249+
from framegrab import FrameGrabber
250+
from framegrab.config import RTSPFrameGrabberConfig
251+
252+
config = RTSPFrameGrabberConfig(
253+
rtsp_url="rtsp://admin:password@192.168.1.100/stream",
254+
backend="gstreamer", # Use GStreamer backend
255+
sample_rate=15, # Optional: limit frame rate
256+
timeout=5.0 # Optional: connection timeout (seconds)
257+
)
258+
259+
with FrameGrabber.create_grabber(config) as grabber:
260+
frame = grabber.grab()
261+
```
262+
263+
Or using YAML/dictionary format:
264+
265+
```yaml
266+
input_type: rtsp
267+
id:
268+
rtsp_url: rtsp://admin:password@192.168.1.100/stream
269+
options:
270+
backend: gstreamer
271+
sample_rate: 15
272+
timeout: 5.0
273+
```
274+
180275
You might have several cameras that you want to use in the same application. In this case, you can load the configurations from a yaml file and use `FrameGrabber.create_grabbers`. Note that currently only a single Raspberry Pi CSI2 camera is supported, but these cameras can be used in conjunction with other types of cameras.
181276

182277
If you have multiple cameras of the same type plugged in, it's recommended that you include serial numbers in the configurations; this ensures that each configuration is paired with the correct camera. If you don't provide serial numbers in your configurations, configurations will be paired with cameras in a sequential manner.
@@ -222,6 +317,7 @@ for grabber in grabbers.values():
222317
display_image(frame) # substitute this line for your preferred method of displaying images, such as cv2.imshow
223318
grabber.release()
224319
```
320+
225321
### Configurations
226322
#### Dictionary or YamlFormat
227323
The table below shows all available configurations and the cameras to which they apply. This is the format used when creating grabbers from dictionaries or yaml strings. If you want to use the python FrameGrabberConfig object, the format is slightly different to optimize for python readability (see format and examples). We use the python pydantic model to validate the dictionary and yaml format
@@ -251,9 +347,11 @@ use the python pydantic model to validate your configuration.
251347
| options.crop.relative.right | 0.9 | optional | optional | optional | optional | optional | optional | optional | optional |
252348
| options.depth.side_by_side | 1 | - | - | - | optional | - | - | - | - |
253349
| options.num_90_deg_rotations | 2 | optional | optional | optional | optional | optional | optional | optional | optional |
254-
| options.keep_connection_open | True | - | optional | - | - | - | optional | optional | - |
255-
| options.max_fps | 30 | - | optional | - | - | - | - | - | optional |
256-
350+
| options.keep_connection_open | True | - | optional (FFmpeg backend) | - | - | - | optional | optional | - |
351+
| options.max_fps | 30 | - | optional (FFmpeg backend) | - | - | - | - | - | optional |
352+
| options.backend | "gstreamer" | - | optional ("ffmpeg" or "gstreamer") | - | - | - | - | - | - |
353+
| options.sample_rate | 15 | - | optional (GStreamer backend) | - | - | - | - | - | - |
354+
| options.timeout | 5.0 | - | optional (GStreamer backend) | - | - | - | - | - | - |
257355

258356
In addition to the configurations in the table above, you can set any Basler camera property by including `options.basler.<BASLER PROPERTY NAME>`. For example, it's common to set `options.basler.PixelFormat` to `RGB8`.
259357

@@ -341,8 +439,26 @@ GenericUSBFrameGrabberConfig:
341439
342440
RTSPFrameGrabberConfig:
343441
additionalProperties: false
344-
description: Configuration class for RTSP Frame Grabber.
442+
description: "Configuration class for RTSP FrameGrabber.\n\nSupports two backends:\n\
443+
- \"ffmpeg\" (default): Uses OpenCV's default FFmpeg backend with drain thread.\n\
444+
\ Options: keep_connection_open, max_fps (for drain rate)\n- \"gstreamer\": Uses\
445+
\ GStreamer backend with zero-buffering (leaky queue) to always\n get the most\
446+
\ recent frame without any buffering delay. Requires OpenCV built with\n GStreamer\
447+
\ support. Options: sample_rate (videorate element), timeout\n\nFFmpeg-specific\
448+
\ options:\n- keep_connection_open: If True (default), keeps connection open with\
449+
\ drain thread for\n low-latency. If False, opens connection only when needed.\n\
450+
- max_fps: Controls drain thread rate (default: 30)\n\nGStreamer-specific options:\n\
451+
- sample_rate: Rate-limit using GStreamer's videorate element (default: None,\
452+
\ no rate limiting)\n- timeout: Connection/data timeout in seconds (default: 5\
453+
\ seconds)\n- protocol: Transport protocol (\"tcp\", \"udp\", or \"tcp+udp\").\
454+
\ If not set, uses GStreamer default."
345455
properties:
456+
backend:
457+
default: ffmpeg
458+
options_key: backend
459+
pattern: ^(ffmpeg|gstreamer)$
460+
title: Backend
461+
type: string
346462
crop:
347463
anyOf:
348464
- additionalProperties: true
@@ -367,7 +483,7 @@ RTSPFrameGrabberConfig:
367483
type: boolean
368484
max_fps:
369485
anyOf:
370-
- type: integer
486+
- type: number
371487
- type: 'null'
372488
default: 30
373489
options_key: max_fps
@@ -385,10 +501,31 @@ RTSPFrameGrabberConfig:
385501
default: 0
386502
options_key: rotation.num_90_deg_rotations
387503
title: Num 90 Deg Rotations
504+
protocol:
505+
anyOf:
506+
- type: string
507+
- type: 'null'
508+
default: null
509+
options_key: protocol
510+
title: Protocol
388511
rtsp_url:
389512
pattern: ^rtsp://
390513
title: Rtsp Url
391514
type: string
515+
sample_rate:
516+
anyOf:
517+
- type: number
518+
- type: 'null'
519+
default: null
520+
options_key: sample_rate
521+
title: Sample Rate
522+
timeout:
523+
anyOf:
524+
- type: number
525+
- type: 'null'
526+
default: 5.0
527+
options_key: timeout
528+
title: Timeout
392529
required:
393530
- rtsp_url
394531
title: RTSPFrameGrabberConfig

‎docker/Dockerfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,4 +41,4 @@ RUN poetry config virtualenvs.create false && \
4141
poetry install --extras youtube --no-root
4242

4343
# Install the project
44-
RUN pip install .
44+
RUN pip install .

‎docker/Dockerfile.gstreamer‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# Dockerfile for framegrab with GStreamer backend support
2+
# Uses system OpenCV (with GStreamer) + dependencies from pyproject.toml
3+
#
4+
# Usage:
5+
# docker build -f docker/Dockerfile.gstreamer -t framegrab-gstreamer .
6+
# docker run -it --rm framegrab-gstreamer pytest test/test_rtsp_integration.py -v
7+
# docker run -it --rm framegrab-gstreamer python sample_scripts/stream_rtsp_with_gstreamer.py <rtsp_url>
8+
9+
FROM debian:bookworm-slim
10+
11+
ENV PYTHONUNBUFFERED=1 \
12+
PIP_NO_CACHE_DIR=1 \
13+
PIP_BREAK_SYSTEM_PACKAGES=1
14+
15+
# =============================================================================
16+
# Layer 1: System dependencies including Python + OpenCV with GStreamer
17+
# =============================================================================
18+
RUN apt-get update && apt-get install -y --no-install-recommends \
19+
# Python and pip
20+
python3 \
21+
python3-pip \
22+
python3-dev \
23+
python-is-python3 \
24+
# System OpenCV with GStreamer support (this is key!)
25+
python3-opencv \
26+
# GStreamer plugins for codec support
27+
gstreamer1.0-tools \
28+
gstreamer1.0-rtsp \
29+
gstreamer1.0-plugins-base \
30+
gstreamer1.0-plugins-good \
31+
gstreamer1.0-plugins-bad \
32+
gstreamer1.0-plugins-ugly \
33+
gstreamer1.0-libav \
34+
# Minimal build tools for some pip packages
35+
build-essential \
36+
pkg-config \
37+
&& rm -rf /var/lib/apt/lists/* \
38+
# Verify GStreamer support immediately
39+
&& python3 -c "import cv2; info=cv2.getBuildInformation(); \
40+
assert 'GStreamer' in info and 'YES' in info.split('GStreamer')[1][:50], \
41+
'OpenCV GStreamer support not available!'; \
42+
print('✓ OpenCV', cv2.__version__, 'with GStreamer support')"
43+
44+
# =============================================================================
45+
# Layer 2: Python dependencies from pyproject.toml
46+
# =============================================================================
47+
WORKDIR /app
48+
49+
# Copy dependency files and source for pip install
50+
COPY pyproject.toml README.md ./
51+
COPY src/ ./src/
52+
53+
# Install framegrab with youtube extra (reads versions from pyproject.toml)
54+
RUN pip3 install --no-cache-dir ".[youtube]"
55+
56+
# Remove pip's opencv-python - we use system OpenCV (has GStreamer support)
57+
RUN pip3 uninstall -y opencv-python opencv-python-headless opencv-contrib-python 2>/dev/null || true
58+
59+
# Downgrade numpy to 1.x - system OpenCV was compiled with numpy 1.x
60+
RUN pip3 install --no-cache-dir "numpy<2"
61+
62+
# Install test dependencies
63+
RUN pip3 install --no-cache-dir pytest psutil
64+
65+
# =============================================================================
66+
# Layer 3: Test files and sample scripts (changes frequently)
67+
# =============================================================================
68+
COPY test/ ./test/
69+
COPY sample_scripts/ ./sample_scripts/
70+
71+
# Verify installation
72+
RUN python3 -c "from framegrab import FrameGrabber; print('✓ framegrab installed')"
73+
74+
# Default command: run GStreamer-related tests
75+
CMD ["pytest", "test/test_rtsp_integration.py", "-v"]

‎pyproject.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "framegrab"
3-
version = "0.14.2"
3+
version = "0.15.0"
44
description = "Easily grab frames from cameras or streams"
55
authors = ["Groundlight <info@groundlight.ai>"]
66
license = "MIT"

0 commit comments

Comments
 (0)