Python API Reference¶
Python bindings for MediaX, generated with SWIG. They cover stream descriptions, the uncompressed payloader, the H.264, H.265 and AV1 encoders and decoders, the RTSP receiver and SAP/SDP announce and discover. Everything else (JPEG, SRT, HLS, Chromecast, GigE Vision, colourspace conversion) is C++ and Qt6 only, see the C++ API and Qt6 API.
Behaviour to know about
- Frames are passed as buffers.
Transmit()accepts any contiguous buffer (bytes,bytearray,memoryview, a C-contiguous numpy array), checks it holds at leastwidth * height * bytes_per_pixelbytes and raisesValueErrorif not. It always blocks until the frame is sent, so theblockingargument is ignored. - Received frames are read with
RtpFrameData.tobytes(), which copies the pixels out.RtpFrameData.cpu_bufferitself is a raw pointer owned by the depayloader. std::string_viewarguments take a Pythonstr.- C++ exceptions are raised as
RuntimeError, they never terminate the interpreter. - Destroying a started payloader or depayloader is safe, but call
Stop()andClose()when you are finished. - Callbacks (
RegisterCallback()and similar) and factories cannot be used from Python, frames are polled withReceive().
Installation¶
Build with -DBUILD_PYTHON=ON (needs swig and the Python 3 development headers), or install the package:
sudo dpkg -i mediax-python_<version>_<arch>.deb
The modules are installed as the mediax package. The location depends on the package format:
| Install | Location |
|---|---|
.deb (Ubuntu) |
/usr/lib/python3/dist-packages/mediax/ |
.rpm (AlmaLinux) |
/usr/lib64/python3.X/site-packages/mediax/ |
make install |
The same directory as the package for the distribution, under <prefix> (for example /usr/local/lib/python3/dist-packages/mediax/) |
The extension modules are compiled for one Python minor version, so the packages depend on exactly that version
(for example python3 (>= 3.12), python3 (<< 3.13)). Build the bindings with the interpreter you will run them with.
The package locations are on Python's default path. For make install add the directory that contains mediax to
sys.path (or set PYTHONPATH), then import the modules from the package:
import sys
sys.path.append('/usr/local/lib/python3/dist-packages')
from mediax import rtp_types
from mediax import sap_listener
import mediax also makes the modules available as attributes (mediax.rtp_types). There is no mediax.rtp
object, the stream types are in rtp_types. Importing by bare module name (import rtp_types with the mediax
directory on sys.path) is not supported, because the modules import each other relatively.
Modules¶
| Module | Built when | Contents |
|---|---|---|
rtp_types |
Always | StreamInformation, RtpFrameData, ColourspaceType_*, StatusCode_*, OutputMode_* |
rtp_payloader |
Always | RtpPayloader base class (abstract) |
rtp_depayloader |
Always | RtpDepayloader base class (abstract) |
rtp_uncompressed_payloader |
Always | RtpUncompressedPayloader |
rtp_uncompressed_depayloader |
Always | RtpUncompressedDepayloader |
rtsp_depayloader |
BUILD_RTSP=ON |
RtspDepayloader, RtspClientConfig, RtspStatistics, RtspTrackInfo, status and state enums, RtpFrameData |
rtp_h264_openh264 |
BUILD_OPENH264=ON |
RtpH264OpenH264Payloader, RtpH264OpenH264Depayloader, EncoderConfig, DecoderConfig |
rtp_h264_vaapi |
libva found | RtpH264VaapiPayloader, RtpH264VaapiDepayloader, VaapiEncoderConfig, VaapiDecoderConfig, profile and level enums |
rtp_h265_vaapi |
libva found | RtpH265VaapiPayloader, RtpH265VaapiDepayloader, VaapiH265EncoderConfig, VaapiH265DecoderConfig, profile and level enums |
rtp_av1_vaapi |
libva found | RtpAv1VaapiPayloader, RtpAv1VaapiDepayloader, VaapiAv1EncoderConfig, VaapiAv1DecoderConfig, Av1Profile |
sap_announcer |
Always | SapAnnouncer (singleton) |
sap_listener |
Always | SapListener (singleton), SdpMessage |
SWIG drops C++ namespaces, so mediax::rtp::ColourspaceType::kColourspaceRgb24 becomes the module-level constant
rtp_types.ColourspaceType_kColourspaceRgb24. Integer types (uint8_t, uint16_t, uint32_t) map to Python int.
Stream Information¶
from mediax import rtp_types
stream_info = rtp_types.StreamInformation()
stream_info.session_name = "python-stream"
stream_info.hostname = "239.192.1.1"
stream_info.port = 5004
stream_info.width = 1920
stream_info.height = 1080
stream_info.framerate = 30
stream_info.encoding = rtp_types.ColourspaceType_kColourspaceRgb24
Fields: session_name, hostname, port, width, height, framerate, encoding, deleted.
Constants¶
# Colourspace / encoding (rtp_types.ColourspaceType_*)
kColourspaceRgb24 # 24-bit RGB
kColourspaceYuv422 # YCbCr 4:2:2 (UYVY)
kColourspaceYuv420p # YUV 4:2:0 planar
kColourspaceMono8 # 8-bit greyscale
kColourspaceMono16 # 16-bit greyscale
kColourspaceRgba # Not a GVA uncompressed video format
kColourspaceBgra # Not a GVA uncompressed video format
kColourspaceNv12 # Not a GVA uncompressed video format
kColourspaceJpeg2000 # JPEG 2000 (not currently implemented)
kColourspaceH264Part4 # H.264 (Part 4)
kColourspaceH264Part10 # H.264 (Part 10)
kColourspaceH265 # H.265 / HEVC
kColourspaceAv1 # AV1
kColourspaceJpeg # JPEG (RFC 2435)
kColourspaceUndefined
# Status (rtp_types.StatusCode_*): kStatusOk (0), kStatusError
# Output mode (rtp_types.OutputMode_*): kStreamOnly, kRecordOnly, kStreamAndRecord
The compressed encodings select the codec of a stream. The payloaders and depayloaders are in the codec modules, see Compressed Video.
Uncompressed Streaming¶
Transmit¶
RtpUncompressedPayloader follows the same lifecycle as the C++ class: SetStreamInfo(), Open() (returns bool),
Start(), Transmit(), Stop(), Close().
from mediax import rtp_types
from mediax import rtp_uncompressed_payloader
stream_info = rtp_types.StreamInformation()
stream_info.session_name = "rgb-stream"
stream_info.hostname = "239.192.1.1"
stream_info.port = 5004
stream_info.width = 640
stream_info.height = 480
stream_info.framerate = 30
stream_info.encoding = rtp_types.ColourspaceType_kColourspaceRgb24
payloader = rtp_uncompressed_payloader.RtpUncompressedPayloader()
payloader.SetStreamInfo(stream_info)
if not payloader.Open():
raise RuntimeError("Failed to open the payloader")
payloader.Start()
frame = bytes(640 * 480 * 3) # RGB24, any contiguous buffer works
if payloader.Transmit(frame) != rtp_types.StatusCode_kStatusOk:
print("Frame not sent")
payloader.Stop()
payloader.Close()
With numpy:
import numpy as np
frame = np.zeros((480, 640, 3), dtype=np.uint8)
frame[:, :, 0] = 255 # Red
payloader.Transmit(np.ascontiguousarray(frame))
The buffer must hold at least width * height * bytes_per_pixel bytes for the configured encoding, otherwise
ValueError is raised. Transmit() always blocks until the frame has been sent.
Other members: SetWidth(), SetHeight(), SetFrameRate(), SetIpAddress(), SetPort(), SetColourSpace(),
SetOutputMode(), SetRecordingFilename(), GetBufferSize() and the matching getters.
Transmit() returns an int, StatusCode_kStatusOk (0) on success.
Receive¶
from mediax import rtp_types
from mediax import rtp_uncompressed_depayloader
stream_info = rtp_types.StreamInformation()
stream_info.hostname = "239.192.1.1"
stream_info.port = 5004
stream_info.width = 640
stream_info.height = 480
stream_info.encoding = rtp_types.ColourspaceType_kColourspaceRgb24
depayloader = rtp_uncompressed_depayloader.RtpUncompressedDepayloader()
depayloader.SetStreamInfo(stream_info)
if not depayloader.Open():
raise RuntimeError("Failed to open the depayloader")
depayloader.Start()
frame = rtp_types.RtpFrameData()
if depayloader.Receive(frame, 1000): # True when a frame arrived within the timeout (ms)
pixels = frame.tobytes() # Copy of the pixels, valid after the next Receive()
print(frame.resolution.width, frame.resolution.height, len(pixels))
depayloader.Stop()
depayloader.Close()
With numpy:
image = np.frombuffer(frame.tobytes(), dtype=np.uint8).reshape((frame.resolution.height, frame.resolution.width, 3))
tobytes() raises ValueError if no frame has been received. To find a stream to receive, use the
SAP listener below.
Compressed Video (H.264, H.265, AV1)¶
Each codec module holds the payloader (encoder and RTP sender) and the depayloader (RTP receiver and decoder) of one
backend. They are used exactly like the uncompressed classes: Transmit() takes an RGB24 frame and Receive()
returns decoded RGB24, the codec work happens inside.
| Codec | Module | Backend | Stream encoding |
|---|---|---|---|
| H.264 | rtp_h264_openh264 |
OpenH264, software | ColourspaceType_kColourspaceH264Part10 |
| H.264 | rtp_h264_vaapi |
Intel/AMD VAAPI | ColourspaceType_kColourspaceH264Part10 |
| H.265 | rtp_h265_vaapi |
Intel/AMD VAAPI | ColourspaceType_kColourspaceH265 |
| AV1 | rtp_av1_vaapi |
Intel/AMD VAAPI | ColourspaceType_kColourspaceAv1 |
The H.264 NVENC, Jetson, V4L2 M2M and Android backends and the Jetson AV1 decoder have no Python module. A module is
only present when its backend was built into MediaX (BUILD_OPENH264=ON for OpenH264, libva for VAAPI), so
from mediax import rtp_h265_vaapi raises ImportError otherwise.
Transmit and receive¶
from mediax import rtp_types
from mediax import rtp_h264_vaapi
stream_info = rtp_types.StreamInformation()
stream_info.session_name = "h264-stream"
stream_info.hostname = "239.192.1.1"
stream_info.port = 5004
stream_info.width = 1280
stream_info.height = 720
stream_info.framerate = 30
stream_info.encoding = rtp_types.ColourspaceType_kColourspaceH264Part10
if not rtp_h264_vaapi.RtpH264VaapiPayloader.IsVaapiAvailable():
raise RuntimeError("no VAAPI device")
# Receiver first, so no frame is missed
rx = rtp_h264_vaapi.RtpH264VaapiDepayloader()
rx.SetStreamInfo(stream_info)
rx.Open()
rx.Start()
tx = rtp_h264_vaapi.RtpH264VaapiPayloader()
tx.SetStreamInfo(stream_info)
tx.Open()
tx.Start()
tx.Transmit(rgb_frame) # bytes, bytearray, memoryview or numpy array, 1280 * 720 * 3 bytes
frame = rtp_types.RtpFrameData()
if rx.Receive(frame, 1000): # True when a decoded frame arrived within the timeout (ms)
pixels = frame.tobytes() # RGB24
tx.Stop()
tx.Close()
rx.Stop()
rx.Close()
The other codecs differ only in the module and class names: rtp_h264_openh264.RtpH264OpenH264Payloader,
rtp_h265_vaapi.RtpH265VaapiPayloader, rtp_av1_vaapi.RtpAv1VaapiPayloader, and the matching ...Depayloader classes.
IsVaapiAvailable() is a static method on the VAAPI classes. The frame buffer must hold at least
width * height * 3 bytes, otherwise Transmit() raises ValueError.
Encoder and decoder configuration¶
The size and frame rate come from SetStreamInfo(). The rest of the encoder is tuned with a configuration object:
| Codec | Encoder configuration | Decoder configuration |
|---|---|---|
| OpenH264 | EncoderConfig: bitrate, keyframe_interval, max_nal_size, enable_denoise, complexity |
DecoderConfig: width, height, error_concealment |
| H.264 VAAPI | VaapiEncoderConfig: bitrate, keyframe_interval, profile (H264Profile_*), level (H264Level_*), qp, enable_cabac, num_ref_frames, max_slice_size |
VaapiDecoderConfig: width, height, error_concealment, max_ref_frames |
| H.265 VAAPI | VaapiH265EncoderConfig: bitrate, keyframe_interval, profile (H265Profile_*), level (H265Level_*), qp, num_ref_frames, ctu_size |
VaapiH265DecoderConfig |
| AV1 VAAPI | VaapiAv1EncoderConfig: bitrate, keyframe_interval, profile (Av1Profile_*), qp, tier, level_idx |
VaapiAv1DecoderConfig |
config = rtp_h264_vaapi.VaapiEncoderConfig()
config.width = 1280 # The configuration also carries the frame size and rate
config.height = 720
config.framerate = 30
config.bitrate = 4000000
config.profile = rtp_h264_vaapi.H264Profile_kMain
config.keyframe_interval = 30
tx.SetStreamInfo(stream_info)
tx.SetEncoderConfig(config) # Call before Open(). The last of SetStreamInfo() and SetEncoderConfig() sets the size
tx.Open()
SetEncoderConfig() replaces the whole configuration, including the frame size and frame rate, and the stream takes
the configured size, so set config.width, config.height and config.framerate as well. SetDecoderConfig() on the
depayloader takes the decoder configuration. Other members: SetMtu() and GetMtu(), ForceIdr() (ForceKeyframe()
for AV1) to make the next frame a key frame, GetBytesSent() and GetFrameCount() on the payloader, and
GetBytesReceived(), GetDecodedWidth() and GetDecodedHeight() on the depayloader.
RTSP Streaming¶
Built with -DBUILD_PYTHON=ON and BUILD_RTSP=ON. The rtsp_depayloader module wraps RtspDepayloader. Callbacks
and factories cannot be expressed in Python, so frames are polled with Receive().
from mediax import rtsp_depayloader as rtsp
config = rtsp.RtspClientConfig()
config.transport = rtsp.RtspTransportMode_kTcp # kAuto, kUdp, kMulticast or kTcp
config.auto_reconnect = True
rx = rtsp.RtspDepayloader()
rx.SetClientConfig(config)
rx.SetUrl("rtsp://camera/stream") # returns RtspStatus_kOk (0) on success
rx.SetCredentials("user", "password")
if rx.Open(): # returns bool
rx.Start()
frame = rtsp.RtpFrameData()
if rx.Receive(frame, 5000): # True when a frame arrived within the timeout (ms)
print(frame.resolution.width, frame.resolution.height)
stats = rx.GetStatistics()
print(stats.reconnects, stats.rtcp_reports_sent)
rx.Stop()
rx.Close()
else:
print(rtsp.RtspStatusToString(rx.GetLastStatus()), rx.GetLastError())
| Member | Description |
|---|---|
SetUrl(url), SetCredentials(user, password) |
Stream URL and optional credentials |
SetClientConfig(RtspClientConfig) |
transport, keepalive, auto_reconnect, max_reconnect_attempts, connect_timeout_ms, response_timeout_ms, media_timeout_ms, enable_rtcp, rtcp_interval_ms, rtp_port_min/rtp_port_max, socket_buffer_bytes, user_agent |
Open(), Start(), Stop(), Close() |
Lifecycle |
Receive(RtpFrameData, timeout_ms) |
Poll for a decoded frame |
GetLastStatus(), GetLastError() |
RtspStatus_* code and text. RtspStatusToString() converts the code |
GetStatistics() |
RtspStatistics: reception, reconnects, rtcp_reports_sent |
GetTracks(), GetTrack(i) |
RtspTrackInfo: index, media_type, control_url, encoding, encoding_name, width, height, framerate, multicast_address, multicast_port, multicast_ttl |
RtpFrameData has resolution (width, height), encoding, cpu_buffer (a raw pointer) and tobytes(), which
returns a copy of the pixels (RGB24 for decoded video). See the RTSP Receiver guide.
SAP/SDP¶
SapAnnouncer and SapListener are singletons. They have no constructor in Python, get the shared object with
GetInstance().
Announcer¶
import time
from mediax import rtp_types
from mediax import sap_announcer
stream_info = rtp_types.StreamInformation()
stream_info.session_name = "announced-stream"
stream_info.hostname = "239.192.1.1"
stream_info.port = 5004
stream_info.width = 1920
stream_info.height = 1080
stream_info.framerate = 30
stream_info.encoding = rtp_types.ColourspaceType_kColourspaceRgb24
announcer = sap_announcer.SapAnnouncer.GetInstance()
announcer.AddSapAnnouncement(stream_info)
announcer.Start()
time.sleep(60) # The announcer runs in a background thread
announcer.Stop()
announcer.DeleteAllSapAnnouncements()
| Method | Description |
|---|---|
AddSapAnnouncement(StreamInformation) |
Add a stream to announce |
DeleteSapAnnouncement(name), UndeleteSapAnnouncement(name) |
Withdraw or restore one announcement by session name |
DeleteAllSapAnnouncements() |
Withdraw every announcement |
Start(), Stop(), Restart(), Active() |
Control and query the announcer |
GetActiveStreamCount() |
Number of active announcements |
GetStreams() |
StreamInformationVector of the announced streams (len(), indexing, iteration) |
SetSourceInterface(index) |
Choose the network interface |
GetInterfaces() |
MapUint32String, interface index to name (items()) |
AddSapAnnouncementRaw(name, sdp_text) and DeleteSapAnnouncementRaw(name) announce a hand-written SDP.
Listener¶
import time
from mediax import sap_listener
listener = sap_listener.SapListener.GetInstance()
listener.Start()
time.sleep(2) # Wait for announcements
for name, sdp in listener.GetSapAnnouncements().items():
print(f"Stream: {name}")
print(f" Address: {sdp.ip_address}:{sdp.port}")
print(f" Resolution: {sdp.width}x{sdp.height}")
print(f" Framerate: {sdp.framerate}")
listener.Stop()
GetSapAnnouncements() returns a SapAnnouncementsMap (a std::map proxy supporting len(), items() and
find()) of SdpMessage. SdpMessage fields: session_name, ip_address, ip_address_source, port, width,
height, framerate, sampling, bits_per_pixel, protocol_version, hash, deleted and sdp_text.
GetStreamInformation(name, StreamInformation) fills the given object and returns True if the stream is known.
RegisterSapListener() and GetSapAnnouncement() (callbacks and out-pointers) are not available from Python, use
GetSapAnnouncements().
Pixel Formats and NumPy¶
Frame buffers are packed, with no row padding. Sizes in bytes are width * height * bytes_per_pixel:
| Encoding | Bytes per pixel | NumPy shape |
|---|---|---|
kColourspaceRgb24 |
3 | (height, width, 3), uint8 |
kColourspaceYuv422 (UYVY) |
2 | (height, width, 2), uint8 |
kColourspaceMono8 |
1 | (height, width), uint8 |
kColourspaceMono16 |
2 | (height, width), uint16 |
These shapes apply to Transmit() input and RtpFrameData.tobytes() output. Reshape with numpy, for example
np.frombuffer(frame.tobytes(), dtype=np.uint8).reshape((height, width, 3)).
Error Handling¶
Most calls report failure through return values rather than exceptions:
| Call | Success |
|---|---|
RtpUncompressedPayloader.Open(), RtspDepayloader.Open(), Receive() |
True |
RtpUncompressedPayloader.Transmit() |
rtp_types.StatusCode_kStatusOk (0) |
RtspDepayloader.SetUrl() |
rtsp.RtspStatus_kOk (0) |
rx = rtsp.RtspDepayloader()
status = rx.SetUrl("rtsp://camera/stream")
if status != rtsp.RtspStatus_kOk:
raise ValueError(rtsp.RtspStatusToString(status))
if not rx.Open():
raise ConnectionError(f"{rtsp.RtspStatusToString(rx.GetLastStatus())}: {rx.GetLastError()}")
Argument problems raise exceptions: TypeError for the wrong type (for example an int where a str is expected, or
str data passed to Transmit()), ValueError for a frame buffer that is too small or an empty RtpFrameData, and
RuntimeError for a C++ exception.
Complete Example¶
Receive an RTSP stream and count frames:
#!/usr/bin/env python3
import sys
import time
sys.path.append('/usr/lib/python3/dist-packages')
sys.path.append('/usr/local/lib/python3/dist-packages')
from mediax import rtsp_depayloader as rtsp
def main(url):
rx = rtsp.RtspDepayloader()
status = rx.SetUrl(url)
if status != rtsp.RtspStatus_kOk:
print(rtsp.RtspStatusToString(status))
return 1
if not rx.Open():
print(rtsp.RtspStatusToString(rx.GetLastStatus()), rx.GetLastError())
return 1
rx.Start()
frame = rtsp.RtpFrameData()
frames = 0
start = time.time()
while time.time() - start < 10:
if rx.Receive(frame, 1000):
frames += 1
if frames == 1:
print(f"First frame {frame.resolution.width}x{frame.resolution.height}")
print(f"{frames} frames, {rx.GetStatistics().reconnects} reconnects")
rx.Stop()
rx.Close()
return 0
if __name__ == "__main__":
sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else "rtsp://camera/stream"))
The installed examples (rtp_transmit_example.py, rtp_receive_example.py, sap_announcer_example.py and
sap_listener_example.py) are in /usr/share/doc/mediax-python/examples/.
Support¶
- Website: https://astutesys.com/support
- Email: support@astutesys.com