Skip to content

SMPTE ST 2110

bilbycast-edge ships first-class support for the broadcast-audio, broadcast-data, and uncompressed-video subset of SMPTE ST 2110:

StandardEssenceStatus
ST 2110-30Linear PCM audio (L16, L24)Phase 1 (shipped)
ST 2110-31AES3 transparent (Dolby E, etc.)Phase 1 (shipped)
ST 2110-40Ancillary data (RFC 8331)Phase 1 (shipped)
ST 2110-20Uncompressed video (RFC 4175, YCbCr 4:2:2 8/10-bit)Phase 2 (shipped)
ST 2110-23Partitioned uncompressed video (2SI + sample-row modes)Phase 2 (shipped)
ST 2110-22JPEG XS videoDeferred (pending libjxs wrapper crate)

The uncompressed video essences reuse the same encoder / decoder infrastructure as the compressed transports: ST 2110-20 / -23 inputs RFC 4175-depacketise raw frames and feed them through video-engine::VideoEncoder in a spawn_blocking worker, then TsMuxer into the flow; outputs demux the flow’s TS, decode via video-engine::VideoDecoder, scale into planar 4:2:2 8/10-bit via VideoScaler::new_with_dst_format, and Rfc4175Packetizer onto the wire (Red + optional Blue). Inputs require a video_encode block and a compiled-in video-encoder-* Cargo feature (libx264 / libx265 / NVENC). Outputs only need the default media-codecs feature.

The accompanying NMOS surface area covers IS-04 (Node API), IS-05 (Connection Management), IS-08 (Audio Channel Mapping), and BCP-004 (Receiver Capabilities). See NMOS for the protocol-level details.

ST 2110 essence flows reuse the existing flow data plane: an input task receives RTP packets, validates them with the essence-specific depacketizer, publishes whole RTP packets to the flow’s broadcast channel, and the output tasks resend them with optional DSCP marking and Red/Blue duplication for SMPTE 2022-7 hitless redundancy. There is no per-sample re-packetization on the loopback path; a paired ST 2110 input + output achieves byte-identical loopback by passthrough.

ST 2110-30 input ──┐
│ broadcast::Sender<RtpPacket>
ST 2110-31 input ──┼──►──────────────────────────────► ST 2110-30 output
│ ST 2110-31 output
ST 2110-40 input ──┘ ST 2110-40 output
SRT output (WAN bridge)
UDP output

bilbycast-edge does not run a PTP daemon itself in the default build. The recommended deployment runs ptp4l from linuxptp on the host with a hardware-timestamping-capable NIC; the edge polls the daemon’s management Unix socket once per second and reports the result through the existing stats channel as FlowStats.ptp_state. Polling is best-effort: when the daemon is unreachable the lock state stays at unavailable and flows continue to operate.

ptp4l ──► /var/run/ptp4l ──► PtpStateReporter ──► PtpStateHandle ──► FlowStats.ptp_state
└─► manager UI

The optional --features ptp-internal flag is reserved for an in-process statime slave clock for environments that prefer a single-process deployment. The flag exists today as a build-matrix marker; the actual statime integration lands in a follow-up.

For broadcast-grade lock (sub-µs offset, traceable holdover) you need a NIC with hardware timestamping. Verified families:

  • Intel i210, i350, X710, E810
  • Mellanox ConnectX-5, ConnectX-6, ConnectX-7

Verify with ethtool -T <iface> — you want hardware-transmit and hardware-receive in the timestamping capabilities.

[global]
domainNumber 0
priority1 128
priority2 128
delay_mechanism E2E
network_transport UDPv4
clockClass 248
free_running 0
freq_est_interval 1
[eth0]

Every ST 2110 input variant accepts an optional redundancy block that points at a second bind address. When set, the input opens both Red and Blue UDP sockets, runs the existing hitless merger over the merged packet stream, and exposes per-leg counters via FlowStats.network_legs (populated only when redundancy is configured). Outputs do the same in reverse: every ST 2110 output accepts a redundancy block that duplicates each packet to a second destination.

┌── Red socket ──┐
RTP source ───► │ │ ──► HitlessMerger ──► broadcast channel
└── Blue socket ─┘

Source-IP allow-listing is supported on the single-leg path. Combining allowed_sources with redundancy is rejected by validation — the merger path doesn’t expose per-packet src and the dual-leg path won’t silently bypass the source filter.

See Configuration for the full schema. Minimal example:

{
"flows": [
{
"id": "audio_stereo",
"name": "Studio A — stereo",
"enabled": true,
"clock_domain": 0,
"input": {
"type": "st2110_30",
"bind_addr": "239.0.0.10:5000",
"interface_addr": "10.0.0.5",
"sample_rate": 48000,
"bit_depth": 24,
"channels": 2,
"packet_time_us": 1000,
"payload_type": 97,
"redundancy": {
"addr": "239.1.0.10:5000",
"interface_addr": "10.1.0.5"
}
},
"outputs": [
{
"type": "st2110_30",
"id": "loop1",
"name": "loopback to monitor",
"dest_addr": "239.2.0.10:5000",
"dscp": 46,
"sample_rate": 48000,
"bit_depth": 24,
"channels": 2,
"packet_time_us": 1000,
"payload_type": 97
}
]
}
]
}
FieldAllowed values
sample_rate48000, 96000
bit_depth16, 24
channels1, 2, 4, 8, 16
packet_time_us125 (AM), 1000 (PM)
payload_type96127
clock_domain0127
dscp063 (default 46 / EF)

ST 2110-21 narrow profile pacing (uncompressed video)

Section titled “ST 2110-21 narrow profile pacing (uncompressed video)”

The default ST 2110-20 / -23 egress path is unpaced — the kernel schedules transmission as fast as the NIC will accept it. That works for development, wide-profile receivers, and most informal deployments. For interop with narrow-profile receivers (Imagine, Lawo, EVS Xeebra, Grass Valley LDX, Bridge Tech VB330 in test mode) the sender must obey the ST 2110-21 §6.3 VRX / Cmax bounds, which at production rates means per-packet timing within a few microseconds of the frame raster.

Userspace pacing can’t hit that budget at 1080p50 (≈ 250 k pps, 4 µs inter-packet) or 4K60 (≈ 1 M pps, 1 µs inter-packet). The solution is Linux SO_TXTIME + the ETF qdisc (Earliest TxTime First), with HW offload on PTP-disciplined NICs.

ST 2110-20 / -23 outputs run the paced sender unconditionally — no per-output opt-in needed. engine::st2110::pacer::St2110_21Pacer computes the per-packet target tx time against the active video raster, anchored on CLOCK_TAI (re-anchored from PTP samples; falls back to monotonic without PTP). The target is handed to engine::wire_emit, which — when BILBYCAST_ENABLE_TXTIME=1 is set on the edge process and a working ETF qdisc is installed on the egress NIC — attaches an SCM_TXTIME CMSG via libc::sendmsg and lets the kernel ETF qdisc honour it. Without the env var the edge stays on the default clock_nanosleep release tier, which still runs the raster math but can’t hit the µs precision narrow profile demands. One std::thread per output socket runs at SCHED_FIFO (granted by the systemd unit’s LimitRTPRIO=99) so the tokio runtime is never blocked.

NIC familyDriverHW offload
Mellanox CX-6 / CX-7mlx5_coreYes (sub-µs jitter)
Intel E810iceYes
Intel i210igbYes
Other(any)Software ETF (~ 1–10 µs jitter)

Software ETF still gives an order-of-magnitude improvement over unpaced. HW offload requires the NIC’s PHC to be PTP-disciplined (phc2sys or equivalent).

The full step-by-step is in Install edge as a Linux service → “ETF qdisc setup” and Wire-Time Precision. Summary:

  1. Install the ETF qdisc on the egress NIC: sudo bash /opt/bilbycast/edge/current/packaging/setup-etf-qdisc.sh enp1s0.
  2. Install the boot-time bilbycast-etf-qdisc@enp1s0.service unit so the qdisc survives reboots:
    Terminal window
    sudo install -m 0644 \
    /opt/bilbycast/edge/current/packaging/bilbycast-etf-qdisc@.service \
    /etc/systemd/system/
    sudo systemctl daemon-reload
    sudo systemctl enable --now bilbycast-etf-qdisc@enp1s0
  3. Confirm ptp4l + phc2sys are running (the system clock must be PTP-disciplined). For HW offload, the NIC’s PHC also needs to be PTP-disciplined via phc2sys.
  4. Opt the edge in to the SO_TXTIME tier by adding BILBYCAST_ENABLE_TXTIME=1 to /etc/bilbycast/edge.env and restarting bilbycast-edge.service. Without this env var the edge stays on the default clock_nanosleep tier, which won’t meet narrow profile.
  5. Confirm the active tier on each output:
    Terminal window
    curl -k https://<edge>:8080/api/v1/stats | \
    jq '.data.flows[].outputs[] | {id: .output_id, tier: .wire_pacing_tier, late: .wire_pacing_late}'

wire_pacing_tier = "so_txtime" plus ETF qdisc installed = tier 1 (or 2 with software ETF). The deprecated wire_pacing config field on ST 2110-20 / -23 outputs is ignored at runtime — pacing is automatic.

ST 2110-20 / -23 outputs always run the paced sender, but on the default clock_nanosleep tier (without BILBYCAST_ENABLE_TXTIME=1) the userspace round-trip can’t hit the µs inter-packet budget that 1080p50 / 4K60 demand. Bytes are valid, frames are decodable. What fails is narrow-profile compliance at the receiver, which manifests as VRX overflow / underflow events on the receiver’s analyzer. There is no in-band degradation visible to a wide-profile receiver. Without PTP, the pacer falls back to monotonic time — outputs run loose relative to absolute time but are still internally even-paced.

The following items are deferred until matching hardware and tooling become available in the test lab:

  • AMWA NMOS Testing Tool runs against IS-04, IS-05, IS-08, BCP-004. Expected pass matrix is documented in NMOS.
  • Live PTP hardware tests on each NIC family above.
  • Real-world interop with Lawo, Riedel, Calrec, Evertz, and Imagine ST 2110 equipment. The manual procedure lives in testbed/ST2110_INTEROP_TEST.md.
  • ST 2110-21 narrow-profile compliance against a hardware analyzer (Bridge Tech VB330, EBU LIST, Telestream PRISM). Today’s pacer ships narrow_linear math; classic gapped narrow lands when a real receiver requires it.