SRT Video (Drones)¶
MediaX sends and receives H.264, H.265 and AV1 video over SRT (Secure Reliable Transport), a UDP protocol that recovers lost packets by retransmission within a fixed latency window. It suits a drone video link: a radio path with a round trip time of tens of milliseconds, bursts of loss and a ground station that needs the picture a few hundred milliseconds after it was taken.
| Property | Value |
|---|---|
| Latency | Low, configurable from 100 to 500 ms (sub-second), constant while the link is healthy |
| Reliability | Self-healing: lost packets are requested again until they could no longer arrive in time, then skipped |
| Security | Optional AES-128/192/256 encryption with a passphrase |
| Payload | H.264, H.265 or AV1 in MPEG-2 transport stream, the format ground control software and ffmpeg expect (see AV1 for the AV1 caveat) |
| Modes | Caller, listener and rendezvous |
The protocol is implemented natively in mediax::srt (draft-sharabayko-srt, live streaming mode). There is no
dependency on libsrt. Encryption uses OpenSSL, if it is not found the library builds without encryption and refuses
streams that ask for it. It interoperates with libsrt (and so with ffmpeg, ffplay, VLC, OBS and srt-live-transmit).
Build option: -DBUILD_SRT=ON (the default, not available on Windows).
How it works¶
Encoder (H.264 / H.265 / AV1) -> TS multiplexer -> SRT sender (ARQ, pacing)
|
| UDP over the lossy radio link
v
SRT receiver (NAK, timed release) -> TS demultiplexer -> RTP packetiser -> decoder -> RGB24
- Every packet is stamped when it is sent. The receiver releases it exactly latency later, so jitter and retransmissions are absorbed by the delay and the picture arrives at a constant rate.
- The receiver reports gaps immediately and then every
max((RTT + 4 x RTTvar) / 2, 20 ms). The sender retransmits ahead of new data. - A packet that cannot be recovered before its release time is skipped (too-late packet drop) and the sender is told, so nothing ever stalls. The video decoder is then held back until the next key frame so a damaged picture is never shown (see Loss handling).
- Sending is paced to the measured input rate plus an overhead for retransmissions, so a burst such as a key frame does not flood a slow radio.
Tuning for a drone link¶
Choose the latency from the round trip time of the radio link. The recommended value is 3 to 4 times the RTT, plus a margin for a burst of loss.
auto config = mediax::srt::SrtConfig::ForDroneLink(/*expected_rtt_ms=*/50);
// latency 260 ms (4 x RTT + 60 ms, limited to 100..500 ms), 30 % retransmission overhead
| RTT | Latency from ForDroneLink() |
Notes |
|---|---|---|
| 10 ms | 100 ms | Floor |
| 25 ms | 160 ms | Short range digital link |
| 50 ms | 260 ms | Typical cellular or long range radio |
| 100 ms | 460 ms | |
| 150 ms or more | 500 ms | Ceiling, raise it by hand if the link needs it |
Other settings worth knowing:
maxbw(bytes per second): the send rate limit. By default it follows the video bit rate plusoheadbwpercent. Set it to the capacity of the radio link when that is known.oheadbw(percent, default 25, 30 with the drone preset): room left for retransmissions. Raise it on lossy links.peeridletimeo(ms, default 5000): how long the link may be silent before it is declared lost.- A key frame interval of one second means a recovery after an unrecoverable loss takes at most one second.
URLs¶
srt://host:port?mode=caller|listener|rendezvous&latency=ms&passphrase=...&pbkeylen=16|24|32&streamid=...
| Option | Meaning |
|---|---|
mode |
caller, listener or rendezvous. Default: listener if the host is empty, otherwise caller |
latency, rcvlatency, peerlatency |
Latency in milliseconds. The largest of the three applies |
passphrase |
Shared secret of 10 to 79 characters. Enables encryption |
pbkeylen |
AES key length in bytes: 16 (default), 24 or 32 |
streamid |
Identifier passed to the listener (up to 512 bytes), e.g. #!::r=drone1,m=publish |
maxbw |
Maximum send rate in bytes per second |
oheadbw |
Retransmission overhead in percent |
mss |
Maximum transmission unit, default 1500 |
fc |
Flow window in packets, default 8192 |
conntimeo |
Connect timeout in milliseconds, default 3000 (a listener waits indefinitely) |
peeridletimeo |
Peer idle timeout in milliseconds, default 5000 |
localport |
Local UDP port of a caller. Used by rendezvous |
drone |
1 applies the drone preset first, explicit options override it |
Units
MediaX latencies are in milliseconds. ffmpeg's latency option takes microseconds: the same 250 ms is
latency=250 here and latency=250000 for ffmpeg.
Connection modes¶
| Mode | Use |
|---|---|
| Caller | The drone calls the ground station, which has a known address (srt://gcs.example:9000). The usual choice when the drone is behind a carrier NAT |
| Listener | The ground station waits for the drone (srt://:9000?mode=listener), or the drone waits for a ground station to pull the video |
| Rendezvous | Both ends connect to each other at the same time, for links where neither can accept connections. Both ends must use the same UDP port |
Direction and mode are independent: a listener can be the sender or the receiver. After a connection ends, a listener accepts the next caller, and a caller reconnects, so a drone that flies out of range and back resumes by itself.
Receiving¶
mediax::srt::SrtDepayloader is a drop-in RtpDepayloader, like the RTSP and HLS receivers. It finds the codec in the
stream, creates the decoder (VAAPI or OpenH264 for H.264, VAAPI for H.265 and AV1) and delivers RGB24 frames.
#include "srt/srt_depayloader.h"
mediax::srt::SrtDepayloader rx;
rx.SetUrl("srt://:9000?mode=listener&latency=250&passphrase=correct-horse-battery");
rx.RegisterErrorCallback([](mediax::srt::SrtStatus status, const std::string& message) {
std::cerr << message << "\n";
});
rx.Open(); // validates the settings
rx.Start(); // returns at once, connects (and reconnects) in the background
mediax::rtp::RtpFrameData frame;
while (rx.Receive(&frame, 5000)) {
// frame.cpu_buffer holds RGB24 data
}
auto stats = rx.GetStatistics(); // RTT, loss, recoveries, drops, reconnects, frames
rx.Close();
For GUI programs use mediax::qt6::QtSrtDepayloader, which delivers frameReady(QImage) signals ("latest wins") and
never blocks the event loop.
Loss handling¶
When packets are lost and cannot be recovered in time, the transport stream has a hole. By default the receiver
discards access units until the next key frame (SetWaitForKeyFrameAfterLoss(true)), freezing the last good picture
instead of showing corrupted ones. GetStatistics() reports ts_continuity_errors and frames_discarded. Pass false
to decode everything and accept artefacts until the next key frame.
Sending¶
mediax::srt::SrtTransmitter muxes access units into MPEG-TS and sends them. Feed it from any encoder that produces
Annex B H.264 or H.265, or AV1 temporal units (see AV1):
#include "srt/srt_transmitter.h"
mediax::srt::SrtTransmitter tx;
tx.SetUrl("srt://gcs.example:9000?latency=250&passphrase=correct-horse-battery");
tx.SetCodec(mediax::rtp::ColourspaceType::kColourspaceH265); // default is H.264
tx.RegisterKeyFrameRequestCallback([&] { encoder.ForceKeyFrame(); });
tx.Open();
tx.Start();
while (capturing) {
tx.SendAccessUnit(nal_data, nal_size, pts_90khz, is_key_frame);
}
tx.Close();
- Parameter sets (SPS, PPS and VPS) are repeated in front of key frames that lack them, and an access unit delimiter is added, so a receiver can join at any key frame.
- With a key frame request callback registered, a new peer is held until a key frame arrives and the callback asks the encoder for one. Without a callback everything is sent.
- If the link cannot take a picture (the peer is not acknowledging), the picture is dropped, the encoder is asked for a key frame and sending restarts there. Nothing queues up, so latency never grows.
MediaX encoders¶
The OpenH264, VAAPI H.264, VAAPI H.265 and VAAPI AV1 encoders can send straight to SRT. Attach() switches off their
RTP output and passes each encoded access unit on:
mediax::rtp::h264::openh264::RtpH264OpenH264Payloader encoder;
encoder.SetStreamInfo(stream_info);
tx.Attach(&encoder);
encoder.Open();
tx.Start();
encoder.Transmit(rgb_frame); // encoded, muxed and sent over SRT
Other encoders can use SendAccessUnit() directly. To attach another RtpPayloader backend, call NotifyEncodedUnit()
from its Transmit() as the four above do.
AV1¶
AV1 travels in the same MPEG-TS and SRT framing as H.264 and H.265. Select it with SetCodec(); the access unit you
send is an AV1 temporal unit, a sequence of OBUs, instead of an Annex B access unit:
tx.SetCodec(mediax::rtp::ColourspaceType::kColourspaceAv1);
tx.RegisterKeyFrameRequestCallback([&] { encoder.ForceKeyframe(); });
tx.Attach(&encoder); // mediax::rtp::av1::vaapi::RtpAv1VaapiPayloader
// or, from any AV1 encoder: tx.SendAccessUnit(temporal_unit, size, pts_90khz, is_key_frame);
- The receiver needs no setting: it recognises AV1 in the transport stream, creates the VAAPI AV1 decoder and delivers RGB24 frames like any other codec. Without VAAPI AV1 decode hardware no decoder is created and an error is reported.
- Each temporal unit is sent starting with a temporal delimiter and with a size field on every OBU. The transmitter adds the temporal delimiter and size fields if the encoder left them out, and repeats the sequence header in front of key frames that lack one, so a receiver can join at any key frame.
- Key frames are shown key frames (
show_frame = 1). They are found from the frame header, soSendAccessUnit(data, size)without akey_frameargument works for AV1 too. - The picture size comes from the sequence header. The loss handling is the same as for H.264 and H.265: after an unrecoverable loss the decoder waits for the next key frame.
- In the transport stream AV1 is private PES data (
stream_type0x06) described by anAV01registration descriptor and an AV1 video descriptor in the PMT, as in the AOM specification "Carriage of AV1 in MPEG-2 TS".
Interoperability
AV1 in MPEG-TS is not widely implemented. ffmpeg 8 neither writes nor reads it (the stream shows as unknown data),
and GStreamer's tsdemux recognises only its own AV1G registration identifier, not AV01. Use AV1 over SRT
between MediaX endpoints. H.264 and H.265 interoperate with ffmpeg, VLC and OBS as before.
The MediaX AV1 depayloader decodes 8-bit 4:2:0 (Main profile) streams only.
RTP to SRT gateway¶
MediaX can carry H.264 and H.265 between RTP and SRT without decoding or re-encoding: the NAL units are only
repacked (RFC 6184 / RFC 7798 RTP on one side, MPEG-TS on the other), so a gateway costs very little CPU and adds no
picture latency of its own. mediax::gateway::SrtGateway does the receiving, converting, retransmitting and announcing;
the application only says what to convert.
#include "gateway/srt_gateway.h"
mediax::gateway::SrtGateway gateway;
// A SAP announced RTP stream goes out over SRT
mediax::gateway::RtpToSrtRouteConfig camera;
camera.session_name = "Camera 1"; // The SAP session name to convert
camera.srt_url = "srt://ground-station.example:9000?latency=200";
gateway.AddRtpToSrt(camera);
// An SRT source is retransmitted as RTP and announced with SAP
mediax::gateway::SrtToRtpRouteConfig drone;
drone.session_name = "Drone 7"; // The name to announce
drone.srt_url = "srt://:9100?mode=listener&latency=300";
drone.rtp_address = "239.192.10.7"; // Multicast or unicast
drone.rtp_port = 5004;
gateway.AddSrtToRtp(drone);
gateway.Start();
for (const auto& route : gateway.GetStatus()) {
// route.state, route.last_error, route.rtp_to_srt.conversion, route.srt_to_rtp.stream ...
}
- RTP to SRT: the route waits for the SAP announcement with the given session name, joins the multicast group (or listens on the unicast address) and port it describes, and sends the video to the SRT URL. A deleted announcement stops the route, a new one resumes it, and a changed address, port or codec restarts it. Both H.264 and H.265 are taken from the announcement.
- SRT to RTP: the route connects to the SRT source and, once the first key frame shows the codec and picture size
(read from the sequence parameter set), starts the RTP output and announces it with SAP. The announcement is
withdrawn when the SRT link drops and made again when it returns. The announced frame rate is
frameratefrom the configuration, as the stream does not carry one. - After a packet loss that could not be recovered, nothing is forwarded until the next key frame, so receivers never get
damaged pictures. On the RTP side the route can ask the source for a key frame through
RtpToSrt::RegisterKeyFrameRequestCallback()if the gateway uses the converters directly. - Any number of routes can run in one process. The gateway registers a catch-all callback with the
SapListenersingleton, which replaces any other catch-all callback the application registered.
Limits of the conversion¶
- The gateway handles H.264 and H.265 only. AV1 is not converted:
RtpToSrt::SetCodec()refuses it withkNotSupportedandSrtToRtpdoes not forward AV1 temporal units. - Frame reordering (B frames) is not supported from RTP to SRT: RTP carries no decode time stamps, so the transport stream gets DTS = PTS. SRT to RTP is not affected.
- RTP packets are not reordered: a gap in the sequence numbers drops the picture it falls in. The first access unit of a stream is dropped, as it may be incomplete.
- Only the non-interleaved RTP modes are supported (single NAL unit, STAP-A / AP and FU-A / FU packets).
- The SAP announcement is the minimal one MediaX produces (picture size, no
sprop-parameter-sets). The parameter sets are sent in the stream in front of every key frame.
Building your own gateway¶
The pieces of SrtGateway can be used on their own, for instance when the RTP comes from somewhere other than SAP:
| Class | Header | Purpose |
|---|---|---|
RtpToSrt |
srt/rtp_to_srt.h |
PushRtpPacket() in, SRT out |
SrtToRtp |
srt/srt_to_rtp.h |
SRT in, RTP packets to a callback, plus a callback with the codec and picture size |
RtpUdpReceiver, RtpUdpSender |
rtp/rtp_udp.h |
Plain UDP unicast/multicast transport for RTP packets |
H264RtpDepacketizer, H265RtpDepacketizer |
mpegts/rtp_depacketizer.h |
RTP packets to Annex B access units |
RtpSapToSrtRoute, SrtToRtpSapRoute |
gateway/gateway_route.h |
One route each way, used by SrtGateway |
Examples¶
# Ground station: wait for a drone, encrypted, tuned for a 50 ms link
srt-receive --url="srt://:9000?mode=listener&passphrase=correct-horse-battery" --drone --rtt=50
# Drone (or a test): send a test pattern
srt-transmit --url="srt://127.0.0.1:9000?passphrase=correct-horse-battery" --drone --rtt=50
# Interop: ffmpeg sends to MediaX
ffmpeg -re -f lavfi -i testsrc=size=1280x720:rate=25 -pix_fmt yuv420p -c:v libx264 -tune zerolatency -g 25 -f mpegts \
"srt://127.0.0.1:9000?mode=caller&latency=250000&passphrase=correct-horse-battery"
# Interop: ffplay receives from MediaX
ffplay "srt://:9000?mode=listener&latency=250000"
Test loss recovery without hardware by impairing the loopback interface:
sudo tc qdisc add dev lo root netem delay 20ms 5ms loss 5%
# ... run srt-transmit and srt-receive, watch lost= and recovered= in the receiver output ...
sudo tc qdisc del dev lo root
Statistics¶
SrtStatistics (also in SrtReceiveStatistics::link and SrtTransmitStatistics::link) reports, per connection:
packets and bytes sent and received, retransmissions, packets lost, recovered, dropped too late and dropped by the
sender, duplicates, ACK and NAK counts, RTT and its variance, the latency in use in each direction and the send and
receive rates.
Limits¶
- Live mode only: no file transfer mode, message mode with multi-packet messages, packet filter (FEC) or connection groups (bonding).
- One key is used for a connection. libsrt refreshes the key after 2^25 packets (about 9 hours at 1000 packets per second), MediaX does not rekey a running connection. Reconnect to rotate the key.
- The receiver decodes the first video stream of the first program. Audio is ignored.
- A transport stream whose video PES packets have no length (many encoders, including ffmpeg's) is delivered one picture late, when the next picture starts. Streams from MediaX carry the length and are delivered at once.
- Rendezvous mode is tested against itself. It could not be tested against libsrt here because ffmpeg binds a rendezvous socket to the peer's address, so it can only rendezvous with a peer on another host.
- Windows is not supported.