Skip to main content

Discover ONVIF cameras, resolve RTSP streams, and play backchannel audio without GStreamer

Project description

RTSP Backchannel for Python

English | 한국어

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
  • ffmpeg on PATH for file playback
  • A camera that exposes an ONVIF sendonly audio 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

rtsp_backchannel-0.2.0.tar.gz (52.8 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

rtsp_backchannel-0.2.0-py3-none-any.whl (53.5 kB view details)

Uploaded Python 3

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

Hashes for rtsp_backchannel-0.2.0.tar.gz
Algorithm Hash digest
SHA256 5c0c3289dc8ec21cbb0f8869e4ee0291c92b59d385f19c9577af60c05c1062e0
MD5 8a922e480d91fe368b77e52ab39d5cc2
BLAKE2b-256 7c7333801b88f33fed5ff12f7ee215a35892a5babca81fc70a4a00d83df64419

See more details on using hashes here.

File details

Details for the file rtsp_backchannel-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for rtsp_backchannel-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 088386f3d5fcd8d86cd579f2a7b7abc9e368629a808b0922e295ae363a7091f2
MD5 1f3d183431d309471f102223623f3d0c
BLAKE2b-256 a7f6cc235454c321d9657cbf3b334e59d29d9fbc6c29a668ae0f9f4287d634ab

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page