Skip to main content

ax-devil banner

ax-devil-rtsp

Python package for receiving RTSP video and AXIS Scene Metadata from Axis cameras, with per-frame capture times for matching the two. Includes a Python API with callbacks and a CLI for quick inspection.

See also ax-devil-device-api and ax-devil-mqtt for related tools.


Install

pip install ax-devil-rtsp

That is all: decoding uses PyAV, whose wheels bundle FFmpeg for Linux, macOS and Windows. No system packages are needed. pip install 'ax-devil-rtsp[display]' adds OpenCV for the CLI's video window.

ax-devil-rtsp doctor shows the PyAV and FFmpeg versions, whether the H.264 and H.265 decoders are present, and which hardware backends are compiled into FFmpeg. It also tries initializing the default hardware device for each backend with both decoders, without contacting a camera:

  • READY: the default device initialized for this decoder.
  • UNAVAILABLE: initialization failed, with the driver or permission error shown.
  • UNSUPPORTED: this decoder has no hardware configuration for that backend.

Hardware decoding is optional and may need system GPU drivers. READY does not guarantee support for a particular stream's profile, bit depth or resolution, and does not prove frames were hardware-decoded. Hardware probe failures do not make doctor fail; missing H.264 or H.265 decoders do.


Python API

import time

from ax_devil_rtsp import SceneMetadata, StreamConfig, StreamSession, VideoOutput, VideoSample, build_axis_rtsp_url

config = StreamConfig(video=VideoOutput.RGB24, metadata=True)
url = build_axis_rtsp_url("192.168.1.90", config, username="root", password="secret", resolution="1280x720")


def on_video(sample: VideoSample) -> None:
    print("frame", sample.data.shape, "captured at", sample.capture_time_ns)


def on_metadata(document: SceneMetadata) -> None:
    print("metadata", len(document.xml), "characters, UtcTime", document.utc_time_ns)


with StreamSession(url, config, on_video=on_video, on_metadata=on_metadata) as session:
    time.sleep(10)
if session.failure:
    print("stream failed:", session.failure)

Sessions

  • A StreamSession is one RTSP connection with a fixed StreamConfig. It receives at most one video and one scene metadata stream. It is one-shot: create a new session to reconnect.
  • start() returns once the camera accepted PLAY. It raises StreamError when the connection, authentication or stream setup fails, or StartCancelledError if stop() was called first. with session: starts, then stops and waits on exit.
  • stop() never blocks and may be called from callbacks. join() waits for the session to end.
  • Sessions are independent; run as many as you need.

Callbacks and threads

Each session runs one receive thread. It reads the socket, reassembles RTP, decodes, converts and calls your callbacks one at a time, in stream order. There are no hidden queues: a slow callback slows only its own session, and the camera's TCP stream backs up instead of memory growing. Hand data to other threads yourself, and drop frames there if your consumer is slower than the camera.

A separate sender keeps RTSP alive even while media is idle or a callback is blocked. It never invokes callbacks and ends with the session. Stopping shuts down the TCP connection immediately rather than waiting to send TEARDOWN.

Every delivered value is owned by the receiver; keep it as long as you like.

If a callback raises, the session stops and that exception becomes session.failure. If the connection, the camera or decoding fails, failure is a StreamError whose message never contains credentials. Either is also passed to on_failure, once, if the session was already running.

What you receive

StreamConfig.video chooses what VideoSample.data is for the whole session:

VideoOutput data
ENCODED Annex-B H.264/H.265 access unit bytes, nothing decoded. session.video_codec says which codec
DECODED av.VideoFrame in the decoder's native pixel format, for your own conversion or GPU upload
RGB24, BGR24 uint8 NumPy array, shape (height, width, 3)
RGBA, BGRA uint8 NumPy array, shape (height, width, 4), ready for Qt Format_RGBX8888 or GPU textures
GRAY uint8 NumPy array, shape (height, width)

VideoSample is generic, so typed callbacks can declare what they get, for example VideoSample[NDArray[np.uint8]]. Each sample also has the frame's rtp_timestamp, keyframe flag and capture_time_ns.

SceneMetadata holds one complete scene metadata XML document as xml, plus its rtp_timestamp and capture_time_ns. Parsing the XML is up to you.

StreamConfig.hwaccel names an FFmpeg hardware device type such as "vaapi" or "cuda" for decoding.

Matching video and metadata

With onvifreplayext=1 in the URL (on by default in build_axis_rtsp_url), the camera sends the capture time of every video frame. sample.capture_time_ns holds it in Unix nanoseconds. A scene metadata document names the frame it describes in its UtcTime attribute; document.utc_time_ns returns it. On Axis cameras the two agree within about a millisecond, the precision of UtcTime. ax-devil-rtsp stream prints this distance every second.

URLs

build_axis_rtsp_url(host, config, ...) builds an Axis axis-media/media.amp URL that asks for exactly the streams in config: analytics=polygon when metadata is requested, video=0 when video is not. Options: username, password, port, camera (video source), resolution and capture_time.

Any other RTSP URL is used as given, including its credentials. Only rtsp:// over TCP is supported. session.name, which logs and errors use, leaves out the URL's credentials and query, since a query can carry access tokens.

If a metadata stream gives 400 Bad Request, the camera's scene metadata producer is probably disabled; enable AnalyticsSceneDescription for that channel, for example with ax-devil-device-api analytics-metadata enable AnalyticsSceneDescription -c 1.

Logging

The package logs through the standard logging module under the ax_devil_rtsp logger and configures no handlers.


CLI

Set AX_DEVIL_TARGET_ADDR, AX_DEVIL_TARGET_USER and AX_DEVIL_TARGET_PASS to skip the device options.

# Video and metadata; prints frame rate, capture-time coverage, metadata rate and sync once per second
ax-devil-rtsp stream --device-ip 192.168.1.90 --device-username root --device-password secret

# Show the video in a window (needs ax-devil-rtsp[display])
ax-devil-rtsp stream --display

# Metadata only, printing each document
ax-devil-rtsp stream --video none --print-xml

# Any RTSP URL, encoded video only
ax-devil-rtsp stream --url "rtsp://root:secret@192.168.1.90/axis-media/media.amp" --video encoded --no-metadata

ax-devil-rtsp doctor

Run ax-devil-rtsp stream --help for every option.


Development

python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"

pytest           # offline: a local fake camera serves real H.264/H.265 and metadata over RTSP
ruff check . && ruff format --check .
mypy

tools/bench.py measures CPU, memory, frame rate, latency and sync for one or more sessions against a real camera.


Disclaimer

This project is an independent, community-driven implementation and is not affiliated with or endorsed by Axis Communications AB. For official APIs and development resources, see the Axis Developer Community.

License

MIT License - see LICENSE for details.

Metadata

Release files for ax-devil-rtsp 0.5.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for ax-devil-rtsp 0.5.1
File Size Uploaded
ax_devil_rtsp-0.5.1.tar.gz 29.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for ax-devil-rtsp 0.5.1
File Interpreter ABI Platform
ax_devil_rtsp-0.5.1-py3-none-any.whl Python 3 none any Details

Total release size: 51.5 kB

Release files / ax_devil_rtsp-0.5.1.tar.gz

Download URL ax_devil_rtsp-0.5.1.tar.gz
Size 29.7 kB
Tags Source
SHA-256 checksum
How to use checksums
e4238f4a7997a67ad7d40caabaaee36c96588d8fcf9da35fb638b8f9755c3936
BLAKE2b-256 checksum
How to use checksums
363b3760bbf043bf5e54b7123a33a1d436b8269de84e29fddb2b4531ba088961
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release files / ax_devil_rtsp-0.5.1-py3-none-any.whl

Download URL ax_devil_rtsp-0.5.1-py3-none-any.whl
Size 21.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
677e5aeff62cb93de834218037a50fde6f597ea5a8125c2f4543ee2df3d5f320
BLAKE2b-256 checksum
How to use checksums
580edbf33ff304fcfe8ca4e0b3be4e16c2359960da933134212d166d9db6e5d0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.12.15

Release history Release notifications | RSS feed

This release

0.5.1 This release

2 release files

0.5.0

2 release files

0.3.0

2 release files

0.2.2

2 release files

0.2.1

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page