Discover ONVIF cameras, resolve RTSP streams, and play backchannel audio without GStreamer
Project description
RTSP Backchannel for Python
Python library and CLI for discovering ONVIF cameras, resolving profile RTSP URIs, and playing one audio file through an ONVIF RTSP backchannel. FFmpeg is required only for file playback; GStreamer is not used.
Other implementations:
The package starts a backchannel session, sends the complete file at real-time
speed, and closes the session. It calls a separately installed ffmpeg
executable to decode input audio. Audio codec handling and RTP/RTSP transport
are implemented in Python. FFmpeg is not bundled or installed by this package.
Requirements
- Python 3.11 or later
ffmpegonPATHfor file playback- A camera that exposes an ONVIF
sendonlyaudio backchannel
Discovery and stream URI lookup do not require FFmpeg.
Installation
Install a released version from PyPI:
python3 -m pip install 'rtsp-backchannel>=0.2,<0.3'
To install the current master source instead of a registry release:
python3 -m pip install \
"git+https://github.com/GagaKor/rtsp-backchannel.git#subdirectory=python"
Install FFmpeg separately when playback is required:
# macOS
brew install ffmpeg
# Ubuntu or Debian
sudo apt-get update
sudo apt-get install ffmpeg
On Windows, install a build from the
FFmpeg download page and add the directory
containing ffmpeg.exe to PATH.
Quick Playback
import os
from rtsp_backchannel import play_file
result = play_file(
host="camera.local",
user="",
password="",
file="/absolute/path/to/event.mp3",
volume=0.05,
)
print(result.packets_sent, result.duration_seconds)
volume must be between 0.0 and 1.0. The tested default is 0.05.
Complete Workflow
Discovery is optional when the camera address is already known. Stream lookup
is useful for inspecting ONVIF Media Profiles, but play_file currently opens
the first profile independently and does not accept a StreamUri selected by
the caller.
import os
from rtsp_backchannel import (
discover_devices,
get_stream_uris,
play_file,
)
password = os.environ["ONVIF_PASSWORD"]
devices = discover_devices(timeout=3.0)
if not devices:
raise RuntimeError("no ONVIF device found")
camera = devices[0]
streams = get_stream_uris(
host=camera.ip,
user="admin",
password=password,
device_urls=camera.xaddrs,
timeout=8.0,
)
for stream in streams:
print(stream.profile_token, stream.profile_name, stream.uri)
result = play_file(
host=camera.ip,
user="admin",
password=password,
file="/absolute/path/to/event.mp3",
volume=0.05,
)
print(result.codec, result.packets_sent, result.duration_seconds)
Public API
discover_devices
discover_devices(
*,
timeout: float = 3.0,
interfaces: list[str] | None = None,
cidrs: list[str] | None = None,
ports: list[int] | None = None,
concurrency: int = 64,
) -> list[DiscoveredDevice]
Without cidrs, this searches local IPv4 interfaces with WS-Discovery.
Omitting interfaces uses addresses detected from hostname resolution and the
default route. interfaces contains local addresses of this computer, not
camera addresses.
Pass IPv4 CIDRs and individual IPv4 addresses in one array to actively search every selected target. Overlapping hosts are probed once:
devices = discover_devices(
cidrs=["10.0.0.0/24", "10.128.0.10"],
timeout=1.0,
ports=[80, 8000, 443],
concurrency=64,
)
CIDR mode sends the unauthenticated ONVIF GetSystemDateAndTime request to
/onvif/device_service. Port 443 uses HTTPS with self-signed certificates
accepted; other ports use HTTP. The default ports are 80, 8000, and 443.
A maximum of 4,096 unique usable IPv4 hosts can be searched per call.
interfaces and cidrs cannot be combined.
Each result contains ip, xaddrs, scopes, and optional name, hardware,
and endpoint_reference fields. Active CIDR results have successful service
URLs in xaddrs, but discovery metadata is normally empty. The networks must
be routable and firewalls must allow the selected ONVIF ports.
get_stream_uris
get_stream_uris(
*,
host: str,
user: str,
password: str,
device_urls: list[str] | None = None,
timeout: float = 8.0,
) -> list[StreamUri]
Authenticates with the ONVIF Device and Media services and returns every Media
Profile's profile_token, optional profile_name, and uri. Credentials are
not inserted into returned RTSP URIs.
play_file
play_file(
*,
host: str,
user: str,
password: str,
file: str,
volume: float = 0.05,
codec: str = "auto",
) -> PlaybackResult
PlaybackResult contains codec, sample_rate, payload_type, rtp_channel,
encoded_bytes, packets_sent, and duration_seconds. Invalid arguments,
authentication failures, network failures, and unsupported camera SDP are
reported as exceptions.
Empty credentials omit ONVIF WS-Security and RTSP authentication. Non-empty ONVIF credentials use PasswordDigest; RTSP credentials are sent after a server challenge. WS-Security digest is authentication, not transport encryption. HTTP and HTTPS, including self-signed TLS compatibility, are supported; use a trusted network or VPN.
The default codec="auto" negotiates SDP in this order: PCMA, PCMU, G726-32,
G726-24, G726-16, G726-40, AAC. The implementation supports G711, RFC3551
G726, and RFC 3640 MPEG4-GENERIC AAC-hbr. MP4A-LATM is explicitly unsupported.
An explicit codec request does not fall back to another codec.
ONVIF can be bypassed with a direct RTSP target:
result = play_file(
host="rtsp://admin:p%40ss@camera.local/backchannel",
user="",
password="",
file="/absolute/path/to/event.mp3",
codec="auto",
)
Embedded credentials are parsed automatically; explicit non-empty arguments
override them. Prefer %40 for @ in a password. Raw @ uses the final
authority separator. Request URIs and logs strip credentials.
CLI
Read the password without echoing it or placing it in shell history:
printf 'Camera password: '
read -rs ONVIF_PASSWORD
printf '\n'
export ONVIF_PASSWORD
Then use the installed command:
# Discover cameras. Output is one JSON object per line.
rtsp-backchannel discover --timeout-ms 3000
# Search explicit interfaces on a multi-NIC or multi-VLAN host.
rtsp-backchannel discover \
--interface 192.0.2.20 \
--interface 198.51.100.20
# Search every host in a CIDR plus one specific IP.
rtsp-backchannel discover \
--cidr 10.0.0.0/24 \
--cidr 10.128.0.10 \
--timeout-ms 1000 \
--port 80 \
--port 8000 \
--concurrency 64
# Resolve RTSP URIs for all ONVIF Media Profiles.
rtsp-backchannel streams \
--host camera.local \
--user admin
# Play one file and close the RTSP session.
rtsp-backchannel play \
--host camera.local \
--user admin \
--pass "$ONVIF_PASSWORD" \
--file '/absolute/path/to/event.mp3' \
--volume 0.05 \
--codec auto
# No ONVIF or RTSP credentials.
rtsp-backchannel play --host camera.local --file '/absolute/path/to/event.mp3'
# Direct RTSP bypasses ONVIF.
rtsp-backchannel play --host 'rtsp://admin:p%40ss@camera.local/backchannel' \
--file '/absolute/path/to/event.mp3'
The play word is optional for backward compatibility. Python's CLI defaults
--user and --pass to empty strings; pass --pass explicitly when the
camera requires credentials.
Playback Behavior
- SDP auto negotiation: PCMA, PCMU, G726-32, G726-24, G726-16, G726-40, AAC
- Supports G711, RFC3551 G726, and RFC 3640 MPEG4-GENERIC AAC-hbr
- MP4A-LATM is explicitly unsupported
- TCP interleaved RTP
- 40 ms audio packets with real-time pacing
- RTSP keepalive during long files
- RTSP teardown after success or failure
The first ONVIF Media Profile must expose a sendonly supported audio track. Audio
output and decoder configuration are camera-specific; a successful RTSP
session does not override disabled or misrouted camera audio output settings.
Development
From the repository root:
PYTHONPATH=python:. python3 -m unittest discover -s python -p 'test_*.py'
python3 -m build python
python3 -m twine check python/dist/*
Release preparation and registry publishing are documented in RELEASING.md.
License
Licensed under either MIT or Apache-2.0, at your option.
This package does not include or link FFmpeg. If an application bundles or redistributes FFmpeg, review the license terms of that FFmpeg build separately. See FFmpeg Legal and THIRD_PARTY_NOTICES.md.
ONVIF is a trademark of ONVIF, Inc. This independent project is not affiliated with or endorsed by ONVIF, Inc. and does not claim ONVIF Profile conformance.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rtsp_backchannel-0.2.0.tar.gz.
File metadata
- Download URL: rtsp_backchannel-0.2.0.tar.gz
- Upload date:
- Size: 52.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5c0c3289dc8ec21cbb0f8869e4ee0291c92b59d385f19c9577af60c05c1062e0
|
|
| MD5 |
8a922e480d91fe368b77e52ab39d5cc2
|
|
| BLAKE2b-256 |
7c7333801b88f33fed5ff12f7ee215a35892a5babca81fc70a4a00d83df64419
|
File details
Details for the file rtsp_backchannel-0.2.0-py3-none-any.whl.
File metadata
- Download URL: rtsp_backchannel-0.2.0-py3-none-any.whl
- Upload date:
- Size: 53.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
088386f3d5fcd8d86cd579f2a7b7abc9e368629a808b0922e295ae363a7091f2
|
|
| MD5 |
1f3d183431d309471f102223623f3d0c
|
|
| BLAKE2b-256 |
a7f6cc235454c321d9657cbf3b334e59d29d9fbc6c29a668ae0f9f4287d634ab
|