Flow Assembly (PID Bus)
A bilbycast-edge flow normally forwards the bytes of its active input out to every output — a passthrough flow. Flow Assembly is the second mode: the flow builds a fresh MPEG-TS from elementary streams pulled off any of its inputs (video from input A, audio from input B, multi-program playout from N sources) and publishes that synthesised TS onto the same broadcast channel every output already consumes.
Every existing output type consumes the assembled TS unchanged — UDP, RTP (with or without 2022-1 FEC / 2022-7 hitless), SRT (incl. bonded / 2022-7), RIST (incl. ARQ), RTMP/RTMPS, HLS, CMAF / CMAF-LL, WebRTC WHIP/WHEP. There is no output-type gate: if a passthrough flow could reach it, an assembled flow can reach it.
Internally the mechanism is a per-flow PID bus: every referenced input demuxes its TS into (input_id, source_pid) → ElementaryStream entries on a lock-free bus, and an assembler subscribes to the slots named in the flow’s assembly block.
What problem it solves
Section titled “What problem it solves”| You want to… | Before | With Flow Assembly |
|---|---|---|
| Mix video from input A with audio from input B as one SPTS out | Required an external mux — the edge couldn’t splice ES between inputs | assembly.kind = spts with two slots, one per input |
| Build an MPTS carrying Studio 1 + Studio 2 from different sources | Had to pre-mux upstream and ingest the finished MPTS | assembly.kind = mpts, two programs each with their own slots |
| Publish a single SPTS built from two redundant ingress legs | SMPTE 2022-7 at the transport layer only (RTP/SRT/RIST) | Pre-bus Hitless source — primary-preference with a 200 ms stall timer, or mode: "seq_aware" for wire-sequence dedup on RTP / RIST legs |
| Swap which PID / input feeds a given program’s audio at runtime | Had to stop + restart the flow | UpdateFlowAssembly — unchanged slots keep running, PMT version bumps mod 32 |
| Operator-driven multi-cam switching with unified output PIDs — receivers don’t re-tune | Switching meant new PMT versions and re-tuning at the receiver | Switch slot — N legs, one out_pid, the manager Switcher’s Take flips legs and the assembler bumps PMT version + DI=1 so receivers stay locked |
Passthrough flows keep working exactly as before. assembly = null (or assembly.kind = passthrough) is the default, and existing configs are unaffected.
Three operating modes — pick one per flow
Section titled “Three operating modes — pick one per flow”A flow runs in one of three modes. All three coexist in the same edge build, all three reach every output type. Pick by the combination of assembly.kind and the slot source types you use.
| Mode | When to pick it | Output PIDs across an input switch | What the Switcher’s ActivateInput does on the wire |
|---|---|---|---|
Passthrough (assembly = null or kind = "passthrough") |
One input is “live” at a time and you want receivers to see whatever PIDs that input declared, byte-for-byte. | Change to whatever the new active input declared. Receivers see new PMT versions and re-tune. The edge’s continuity fixer cushions CC + PMT version + DI to keep cutover seamless. | Flips which input’s bytes are forwarded. Classic broadcast switcher behaviour. |
Assembly without Switch slots (kind = "spts"/"mpts", slots use pid / essence / hitless) |
You want a fresh PMT layout — unified output PIDs, e.g. always video on 0x100 — built from one input, or from a 2022-7-style redundant pair. Every input contributes ES simultaneously. |
Stay unified. Each slot’s out_pid is fixed by the assembly. |
No-op for the data path. Every input contributes ES simultaneously regardless of which is “active”. |
Assembly with Switch slots (kind = "spts"/"mpts", one or more slots use switch) |
Operator-driven N-input switching with unified output PIDs — the Switcher’s PGM/PVW/Take drives which input feeds the slot. All N legs subscribe concurrently (warm), so cutover is instant. | Stay unified. The slot’s out_pid is fixed; only the source leg flips. PMT version bumps mod 32 + DI=1 fires on the next PCR for the affected out_pid so receivers re-anchor STC without re-tuning. |
Flips the active leg of every Switch slot whose leg list contains the named input. Slots without that input as a leg stay on their current leg. |
The three modes can be mixed within an MPTS — one program can carry explicit pid slots, another can use hitless for redundancy, a third can use switch for operator-driven multi-cam. PIDs always behave per the slot source type.
Assembly kinds
Section titled “Assembly kinds”| Kind | What it builds | Programs | PCR requirement |
|---|---|---|---|
passthrough |
No assembly. Forwards the active input’s bytes. Runtime-equivalent to assembly = null. |
Must be empty | None |
spts |
Single-program TS from selected ES slots. | Exactly one | Flow-level or program-level pcr_source (program-level wins) |
mpts |
Multi-program TS, fresh PAT listing every program, one synthesised PMT per program. | One or more, unique program_number per program |
Every program needs an effective pcr_source (its own, or the flow-level fallback) |
Minimal SPTS example — video from input A + audio from input B
Section titled “Minimal SPTS example — video from input A + audio from input B”{ "id": "mixed-feed", "name": "Mixed Feed", "input_ids": ["cam-a", "mic-b"], "output_ids": ["udp-out", "srt-out"], "assembly": { "kind": "spts", "pcr_source": { "input_id": "cam-a", "pid": 256 }, "programs": [ { "program_number": 1, "service_name": "Mixed", "pmt_pid": 4096, "streams": [ { "source": { "type": "pid", "input_id": "cam-a", "source_pid": 256 }, "out_pid": 256, "stream_type": 27, "label": "Video (cam A)" }, { "source": { "type": "essence", "input_id": "mic-b", "kind": "audio" }, "out_pid": 257, "stream_type": 15, "label": "Audio (mic B)" } ] } ] }}Minimal MPTS example — two programs from three inputs
Section titled “Minimal MPTS example — two programs from three inputs”"assembly": { "kind": "mpts", "pcr_source": { "input_id": "cam-a", "pid": 256 }, "programs": [ { "program_number": 1, "service_name": "Studio 1", "pmt_pid": 4096, "pcr_source": { "input_id": "cam-a", "pid": 256 }, "streams": [ { "source": { "type": "pid", "input_id": "cam-a", "source_pid": 256 }, "out_pid": 256, "stream_type": 27 }, { "source": { "type": "essence", "input_id": "mic-a", "kind": "audio" }, "out_pid": 257, "stream_type": 15 } ] }, { "program_number": 2, "service_name": "Studio 2", "pmt_pid": 4112, "pcr_source": { "input_id": "cam-b", "pid": 256 }, "streams": [ { "source": { "type": "pid", "input_id": "cam-b", "source_pid": 256 }, "out_pid": 272, "stream_type": 36 }, { "source": { "type": "pid", "input_id": "cam-b", "source_pid": 257 }, "out_pid": 273, "stream_type": 15 } ] } ]}Slot sources — where the bytes come from
Section titled “Slot sources — where the bytes come from”Every slot in a program’s streams[] picks its source from one of four variants:
"pid"— explicit PID off a named input:{ "type": "pid", "input_id": "...", "source_pid": 256 }. Use when the operator knows the exact upstream PID (from the input’s live PSI catalogue, or a written spec)."essence"— first elementary stream of a given kind off a named input:{ "type": "essence", "input_id": "...", "kind": "video" | "audio" | "subtitle" | "data" }. Useful when the upstream is single-program and the operator just wants “its video” / “its audio” without binding to a specific PID. Resolves at flow start against the input’s PSI catalogue, and re-resolves on everyUpdateFlowAssembly."hitless"— pre-bus merger of a redundant leg pair, primary-preference by default:{ "type": "hitless", "primary": { <pid|essence> }, "backup": { <pid|essence> } }. A merger task subscribes to both legs and forwards the primary verbatim; if no primary packet arrives for 200 ms it flips to the backup, and the first primary packet to arrive after that flips it straight back. Config validation accepts apidoressenceleg (and rejects a nested Hitless), but the runtime resolvespidlegs only — anessenceleg fails flow bring-up withpid_bus_hitless_leg_not_pid."switch"— operator-driven N-input switch (1..=64 legs):{ "type": "switch", "legs": [ { "type": "pid"|"essence", "input_id": "...", ... } ], "initial_input_id": "..." }. All legs subscribe concurrently (warm) so cutover is instant; the assembler forwards bytes only from the leg whoseinput_idmatches the flow’s currently-active input. The Switcher’sActivateInput(PGM/PVW/Take) flips every Switch slot whose leg list contains the named input — slots without that input as a leg are silent. Output PIDs stay unified across switches (the slot’s fixedout_pid); PMT version bumps mod 32 and DI=1 fires on the next PCR for thatout_pidso receivers stay locked without re-tuning. The active leg survives flow restart viaflow.active_input_id; if the saved active input is no longer in the leg list, the slot silently falls back toinitial_input_id.
The hitless slot source takes a mode. The default, primary_preference, is arrival-timing failover: forward the primary verbatim, flip to the backup once the stall timer expires. seq_aware is true SMPTE 2022-7 — the merger dedups and gap-fills against the upstream wire sequence number carried on each leg’s packets, switching sub-frame instead of after a stall.
seq_aware needs both legs to resolve to inputs that stamp an upstream sequence number, and today that means RTP and RIST inputs only — SRT, UDP, bonded, WebRTC and the synthetic inputs carry none. That pairing is not checked at config-save time: a seq_aware slot on legs that carry no sequence forwards the primary with a log warning and silently discards every backup packet. The edge advertises the hitless_2022_7 capability when the mode is available, but no manager surface exposes it yet — today it is reachable only by editing the config JSON.
| Field | Applies to | Range | Default | What it does |
|---|---|---|---|---|
mode |
both | primary_preference | seq_aware |
primary_preference |
Failover algorithm. |
stall_ms |
primary_preference |
20..=5000 ms |
200 | How long with no primary packet before the merger flips to the backup. Ignored in seq_aware. |
path_differential_ms |
seq_aware |
5..=2000 ms |
none | Skew-accommodation buffer. Set it and the merger holds every packet this long after first arrival before releasing it in sequence order — the slower leg gets its full window and a single-leg loss inside it is filled hitlessly. Omit it and the merger runs dedup-only: lower latency, no asymmetric-path protection. |
reorder_window |
seq_aware |
multiple of 64 in 64..=4096 |
— | Accepted and range-checked, but inert. The configured value never reaches the merger, whose gap-fill window is fixed at 1024. |
For dual-leg dedup on legs the bus cannot sequence — SRT, UDP, bonded — use the input’s own SMPTE 2022-7 redundancy block instead: RTP, SRT and RIST inputs each carry one, and assembly sits on top of that unchanged.
Switch slot example — three-camera multi-cam bus on a single video PID
Section titled “Switch slot example — three-camera multi-cam bus on a single video PID”{ "out_pid": 256, "stream_type": 27, "source": { "type": "switch", "legs": [ { "type": "essence", "input_id": "cam-a", "kind": "video" }, { "type": "essence", "input_id": "cam-b", "kind": "video" }, { "type": "essence", "input_id": "cam-c", "kind": "video" } ], "initial_input_id": "cam-a", "splice_mode": "pes_aligned", "splice_budget_ms": 2000 }}To drive it from the Live Switcher, build a preset for each camera with a single activate_input action (target this flow + the camera’s input id) and Take. Receivers stay locked through every cut — the output PID is always 0x100 regardless of which camera is live. Hitless and Switch slots can coexist in the same assembly (e.g. one Hitless slot for an auto-failover audio pair plus one Switch slot for the multi-cam video bus).
pid_bus_switch_slot capability: the manager UI’s Switch source-type option in the Node Bus Matrix is gated on the edge advertising this capability. Older edges hide the option; the operator gets the same Pid / Essence / Hitless choices they always had.
Splice strategy — splice_mode
Section titled “Splice strategy — splice_mode”Each Switch slot picks how ActivateInput lands on the wire:
splice_mode |
What happens on Take | When to pick it |
|---|---|---|
pmt_bump (default) |
PMT version bumps mod 32 and DI=1 is armed on the next PCR for the slot’s out_pid. Receivers re-acquire if the two sources are independent encoders of the same content. Backwards-compatible default — every pre-PES-Switch flow gets this. |
When the two legs are unrelated programs (different content) or when receivers are known-tolerant. |
pes_aligned |
Hold the outbound stream at the from-leg’s last fully-emitted PES boundary, wait up to splice_budget_ms for the to-leg to produce a clean access-unit boundary, then concatenate. Audio splices wait for the next PUSI=1 PES with a monotonically-past PTS; video splices additionally require an H.264 / HEVC IDR. On budget exhaustion the path falls back to pmt_bump and emits pes_splice_timeout. |
When both legs are coherent content (multi-cam program feeds, redundant encodes of the same source) and the receivers must stay glitch-free. |
splice_budget_ms defaults to 200 ms for audio and 2000 ms for video (≈ one typical broadcast GoP). Range 20..=5000.
Codec-parameter sentinels (PES-aligned only): the edge snapshots the from-leg’s codec parameters (AAC AudioSpecificConfig — profile / sample rate / channel config — for stream_type 0x0F ADTS and 0x11 LATM; H.264 SPS — profile / level / chroma / bit-depth / resolution — for 0x1B; HEVC SPS — same fields — for 0x24) on each access-unit boundary, and parses the to-leg’s first commit-eligible PES. On mismatch the splice refuses and the assembler falls back to pmt_bump with a structured Warning pes_splice_codec_param_mismatch event carrying both A and B’s full parameter sets — instead of producing inaudible / undecodable output. Either side None (no parseable header) is fail-safe — the splice commits on PTS / IDR alone.
Per-switch override — splice_mode_override
Section titled “Per-switch override — splice_mode_override”POST /api/v1/flows/{flow_id}/activate-input (and the manager-proxied POST /api/v1/nodes/{id}/flows/{flow_id}/activate-input) accepts an optional splice_mode_override: "pmt_bump" | "pes_aligned". The override beats every Switch slot’s config-time splice_mode for this one switch only — the persisted assembly is untouched. Useful for an emergency “force PMT-bump on a Take we don’t care about hand-holding” or “force PES-aligned for the one shot that matters”. The manager Switcher preset editor surfaces this as a splice: default | force PMT-bump | force PES-aligned dropdown per preset action.
PCR rules
Section titled “PCR rules”- SPTS — exactly one PCR reference is required. Either set
assembly.pcr_sourceat the top of the assembly, or setpcr_sourceon the one program. If both are set, program-level wins. The referenced(input_id, pid)must resolve to a concrete slot (or an Essence slot’s input) inside the program, otherwise the flow fails to start withpid_bus_pcr_source_unresolved. - MPTS — every program needs an effective PCR, whether from its own
pcr_sourceor from the flow-level fallback. Validation catches a program with neither at config-save time. Per-program PCR enforces the H.222.0 rule that a program’sPCR_PIDmust be one of its own ES PIDs. - The chosen PCR rides byte-for-byte onto the assembled TS; the synthesised PMT’s
PCR_PIDfield points at the corresponding slot’sout_pid.
Input requirements — what can feed the bus
Section titled “Input requirements — what can feed the bus”Every input referenced by any slot must either already produce MPEG-TS on the broadcast channel, or be configured so the runtime can wrap it into TS before publishing to the bus.
Inputs that produce TS natively (always eligible):
SRT, UDP, RTP (with is_raw_ts: true), RIST, RTMP (after the built-in FLV→TS muxer), RTSP, WebRTC WHIP/WHEP, ST 2110-20, ST 2110-23, Bonded, TestPattern, media_player, replay, sdi, mxl_video, and mosaic (the multiviewer wall canvas, on multiviewer builds).
PCM / AES3 inputs that become TS when audio_encode is set on the input:
| Input | Eligible audio_encode.codec |
|---|---|
| ST 2110-30 (L16 / L24 PCM) | aac_lc, he_aac_v1, he_aac_v2, s302m |
rtp_audio (RFC 3551 PCM over RTP) |
aac_lc, he_aac_v1, he_aac_v2, s302m |
| ST 2110-31 (AES3 transparent — Dolby E, etc.) | s302m only (the 337M sub-frames ride through the 302M wrap bit-for-bit) |
Without audio_encode set, an assembly referencing one of these three inputs fails bring-up with pid_bus_spts_input_needs_audio_encode.
mxl_audio (Float32 PCM) has the same shape — the eligibility gate counts it as a TS carrier only when audio_encode is set — with two caveats. The MXL audio ingest does not yet encode or republish. With audio_encode set the input is admitted onto the bus and then publishes nothing at all — no event, no error — so a slot pointed at one stays silent. (Without it the input task does raise a Warning mxl_audio_no_encode_set at startup, but by then the assembly has already been refused at bring-up.) And an mxl_audio input with no audio_encode reports pid_bus_spts_non_ts_input, not pid_bus_spts_input_needs_audio_encode — only the three inputs in the table above raise the latter.
Inputs with no current path to TS: ST 2110-40 and mxl_anc (both RFC 8331 ancillary data) — wrapping ANC into TS is deferred; referencing one emits pid_bus_spts_non_ts_input.
Codec support on the decoded-ES cache: aac_lc, he_aac_v1, he_aac_v2, s302m. mp2 and ac3 parse and validate successfully but fail loudly at flow bring-up with pid_bus_audio_encode_codec_not_supported_on_input until the matching muxer wrappers land.
Runtime behaviour
Section titled “Runtime behaviour”- The assembler subscribes to the per-ES bus (
(input_id, source_pid) → EsPacket), rewrites each 188-byte TS packet’s PID to the configuredout_pid, stamps a per-out-PID monotonic continuity counter, bundles 7 TS packets into MTU-safe 1316-byte RTP packets, and publishes them onto the flow’s existing broadcast channel — exactly where a passthrough forwarder would. - PAT and PMT are synthesised on a 100 ms cadence. When the PAT set changes,
PAT.version_numberbumps mod 32. When a program’s slot composition orpcr_sourcechanges, that program’sPMT.version_numberbumps mod 32 — both counters advance monotonically across swaps to avoid phantom-version collisions. - Each slot’s ES_info descriptor loop is copied through from its source PMT, re-resolved per input from the live PSI catalogue on every PSI tick. Only a whitelist survives — registration
0x05, ISO-639 language0x0A, stream identifier0x52, teletext0x56, subtitling0x59, DVB AC-30x6A, E-AC-30x7A, DTS0x7B, AAC0x7C, extension0x7F; CA and network-private tags are stripped, because the assembled mux is not the source mux. A change to a slot’s descriptor set bumps that program’sPMT.version_number. The PMT is emitted as a single TS packet (section_length≤ 180), so descriptor loops are admitted greedily in slot order and any slot whose loop would overflow emitses_info_length = 0rather than splitting the section. - PCR rides onto the TS byte-for-byte from the referenced slot’s source packets.
- A 10 ms safety-net flush keeps partially-filled bundles shipping during sparse periods (audio-only idle, keyframe gaps) so downstream sockets never see multi-second silence.
- Backpressure: slot fan-ins are
broadcast::Receiver<EsPacket>. Slow consumers drop rather than stall the demuxer — the same lock-free, never-block-the-data-path discipline used everywhere else in the edge.
Hot-swap — UpdateFlowAssembly
Section titled “Hot-swap — UpdateFlowAssembly”The assembly plan is hot-swappable. A manager UpdateFlowAssembly WS command — or a direct PUT /api/v1/flows/{flow_id}/assembly REST call on the edge — replaces the running plan without tearing the flow down:
- Slots that are unchanged keep their existing bus fan-in tasks — no packet gap.
- Slots whose
source,out_pid,stream_type, orlabelchanged have their fan-ins re-spawned; fan-ins for removed slots are cancelled. - Per-program
PMT.version_numberbumps for any program whose composition or PCR source changed. PAT.version_numberbumps only when the set of programs changed (added / removed / renumbered).- PSI is re-emitted immediately on swap so receivers see the new PMT before any packet lands on a new
out_pid. - The new assembly is persisted to
config.jsononly after the swap succeeds. A no-op swap (incoming plan deserialises byte-equal to current) is a silent short-circuit. - Transitions across the passthrough boundary (passthrough ↔ spts/mpts) are rejected. Those require a full
UpdateFlowround-trip because the plumbing on the flow changes (bus + assembler spawn vs. direct broadcast). - A cross-input
pcr_sourcechange is rejected while the flow’s master clock is genlocked to it (source_pcr_pll/contribution). The PLL sampler and the fallback watcher pin that input at flow start and cannot re-key in place, so the swap fails with Criticalpid_bus_pcr_source_change_requires_restart(details = { old_input_id, new_input_id, master_clock_kind }). Re-issue the edit as a fullUpdateFlow(restart), or setmaster_clock.kind = "wallclock"on the flow to make it hot-retargetable. Under wallclock / PTP / audio-master the re-point hot-swaps normally — the pinnedpcr_sourcethen only decides which slot carries PCR. Moving to a different PID within the same input always hot-swaps.
Input-host flows — sharing an input across the node
Section titled “Input-host flows — sharing an input across the node”A flow can declare output_ids: [] (no consumers) so its only job is to own an input and host it on the node’s elementary-stream bus for sibling flows to subscribe to. The pattern has no special config — the engine just notices that another flow’s assembly references one of its inputs and shares the underlying demuxer via refcount.
When to use it:
- A single ingress feeds N downstream contribution / distribution flows on the same edge — host the ingress once, let the assembled outputs subscribe instead of opening N parallel decoders.
- A “compliance-only” recording stays on its own flow with no outputs but its input is the source of truth that a separate live-egress flow assembles from.
- Mixed bilbycast-edge installs running on cloud infra where the same upstream SRT contribution feeds a redaction pod, a public distribution pod, and a compliance recorder — only one decoder runs.
The shared-demuxer refcount is managed by the engine. When the host flow stops while sibling assemblies still reference its inputs, the node-wide ES bus keeps the channel armed (it is append-only) and the affected slots simply go quiet: each latches a Warning pid_bus_slot_stalled after ~5 s with no ES — structured { input_id, source_pid, program_number, out_pid, seconds_since_data } — so the operator sees why the assembled output went silent, and an Info pid_bus_slot_recovered clears the latch when the host flow restarts. (The scan runs only on continuous-media stream_types, with a 10 s grace after assembler start, so sparse essences never raise a false alarm. pid_bus_slot_source_closed is a defensive tripwire on the same fan-in that cannot fire while the bus holds its senders — don’t wait for it.) No manual intervention is needed on restart.
Cross-flow references are scoped to the same node. To share across nodes, use one of the IP transports (SRT, RIST, RTP, ST 2110) explicitly.
ClockIdentity preflight on PES-aligned splices
Section titled “ClockIdentity preflight on PES-aligned splices”When an assembly’s Switch slot is configured with splice_mode: "pes_aligned" and the two legs come from inputs slaved to different PTP grandmasters (or one slaved + one wallclock), splicing them at the PES boundary will produce a STC jump on the wire that receivers can’t ride through. The assembler runs a ClockIdentity preflight on every ActivateInput: it compares the per-input PTP clockIdentity (from the input’s PTP state reporter) against the assembly’s master clock kind, and if the two legs disagree it refuses the PES-aligned splice and falls back to pmt_bump with a structured Warning pid_bus_master_clock_mismatch event.
This is the layer above the codec-parameter sentinels — the sentinels catch “the two encoders produce different SPS/AudioSpecificConfig”, ClockIdentity catches “the two encoders are running off different time references”. Both must pass before the splice commits.
The preflight has no effect on pmt_bump splices (the default) — those tolerate the STC jump by design.
Interaction with output-level PID remap (pid_map)
Section titled “Interaction with output-level PID remap (pid_map)”The assembly owns the PID layout of the TS it produces — every slot’s out_pid, every program’s pmt_pid, whichever slot got selected as PCR. An output’s pid_map applies after the assembly on the way out, so the same assembled PID layout can be published and then re-labelled per output if an external downstream has hard-coded PID expectations. Prefer picking the right out_pid in the assembly directly — pid_map is an escape hatch for downstream constraints you can’t change.
Monitoring
Section titled “Monitoring”A running assembled flow exposes:
- Flow-card badge in the manager UI —
SPTS ASSEMBLED/MPTS ASSEMBLED(cyan). - Assembled Output section on the flow card — one sub-table per program listing each slot’s
out_pid,stream_type, resolved kind, source label (orHitless(A/B)), live bitrate, packets, CC errors, PCR discontinuity counters fromFlowStats.per_es[]. - Per-output PCR trust —
p50 / p99columns on the Outputs table, fed byOutputStats.pcr_trust. The sampler records|ΔPCR_µs − Δwall_µs|on successful sends of PCR-bearing TS packets into a rotating 4096-sample reservoir and exposes p50 / p95 / p99 / max. - Flow-rollup PCR trust —
FlowStats.pcr_trust_flow(Samples, p50 / p95 / p99 / Max, window-p95) rendered at the bottom of the flow card. - Events —
pid_bus_*andpes_splice_*codes ride as events at mixed severity: Critical for bring-up and hot-swap refusals, Warning for runtime degradation (pid_bus_slot_stalled,pid_bus_stream_type_mismatch,pid_bus_pid_cap_reached,pes_splice_degraded,pes_splice_timeout,pes_splice_codec_param_mismatch), Info for recovery (pid_bus_slot_recovered,pes_splice_completed). Each carries structureddetails(error_code,input_id,input_type,program_number, …) so the manager UI can highlight the offending field on Create/Update modals without parsing the error string. See Events & Alarms — PID bus / Flow Assembly.
Validation rules
Section titled “Validation rules”All enforced at config-save time (plus belt-and-braces checks at flow bring-up). None of these can slip past to runtime:
passthroughmust have emptyprogramsand nopcr_source.sptsmust have exactly one program.mptsmust have at least one program; allprogram_numbervalues unique, allpmt_pidvalues unique.- Every referenced
input_idmust name an input in the node’s top-levelinputsarray — not necessarily one of this flow’s owninput_ids. Cross-flow references are legal; that is exactly what the Node Bus Matrix authors. An id that exists nowhere on the node is rejected on a whole-config push, but theUpdateFlowAssembly/PUT /api/v1/flows/{flow_id}/assemblyhot-swap path does not re-run that node-wide existence check. Apidslot pointed at an input that exists nowhere simply stays silent (and latchespid_bus_slot_stalledafter 5 s); anessenceslot fails the swap after the 3 s resolution window with Criticalpid_bus_essence_no_catalogue. program_numbermust be> 0(0 is reserved for the NIT).pmt_pidand everyout_pidmust be in0x0010..=0x1FFE(reserved PIDs and the NULL PID are refused).- Within a program, every
out_pidmust be unique and must not equal that program’spmt_pid. service_name≤ 128 chars; slotlabel≤ 256 chars.- SPTS: flow-level
pcr_sourceor the one program’spcr_sourcemust be set. - MPTS: every program’s effective
pcr_source(own or flow-level fallback) must be set. - When
pcr_sourceresolves concretely, it must hit one of that program’s slots (Pid match) or one of its Essence-slot inputs. - Hitless nested inside another Hitless is rejected.
- Switch slot rules:
legs.lengthin1..=64; no two legs may share identity ((input_id, source_pid)forpidlegs,(input_id, kind)foressencelegs);initial_input_idmust match at least one leg’sinput_id; when every leg isessence-typed, allkindvalues must agree; Switch nested inside Hitless is rejected; Switch nested inside Switch is type-system impossible. - Non-TS inputs without a valid
audio_encodeare rejected at flow bring-up with a specificpid_bus_*error code.
Related
Section titled “Related”- Node Bus Matrix — the manager-side three-pane authoring surface for the node-wide ES bus. Replaces the per-flow assembly form. Click-to-wire + drag-and-drop, pending-state diffing, salvo export to a Switcher preset.
- Live Switcher — the PGM/PVW director console that drives
ActivateInputacross flows. Drives Switch-slot active legs in assembled flows in addition to its legacy passthrough behaviour. - MPTS → SPTS filtering — the simpler story: forward an upstream MPTS verbatim and optionally down-select a single program per output. Complementary to Flow Assembly — assembly builds fresh TS from elementary streams; filtering re-packs an existing TS.
- Events & Alarms — the
pid_bus_*error code reference (including the Switch-slot codes andpes_splice_*events). - Hot Input Switching — format-agnostic zero-gap cutover between a flow’s inputs, via
TsContinuityFixer, for passthrough flows.