Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions .github/workflows/ts.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,15 @@ jobs:
MOQ_TEST_RUNS: ${{ runner.temp }}/moq-test-runs
shell: bash -leo pipefail {0}

# A KLV stream joins the program mid-stream and leaves it: the export announces each
# layout at the next PMT version, and carries the stream only while a PMT lists it.
- name: TS PMT revision
if: ${{ !cancelled() }}
run: nix develop --command just test ts --revision --strict
env:
MOQ_TEST_RUNS: ${{ runner.temp }}/moq-test-runs
shell: bash -leo pipefail {0}

- name: TS audio-only SCTE-35
if: ${{ !cancelled() }}
run: nix develop --command just test ts --clockless audio-scte35
Expand Down
6 changes: 4 additions & 2 deletions doc/bin/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,10 @@ supplies a PES boundary or clock fails rather than retaining bytes indefinitely.
It takes one program: a multi-program
stream is refused until `--program` picks one, or `--program all` publishes
each as its own broadcast (`event.hang` becomes `event/1.hang`,
`event/2.hang`). A damaged packet is dropped on its own PID, so video freezes
until its next keyframe while the other tracks carry on. The importer also
`event/2.hang`), following the PAT: a program it adds publishes, one it drops
ends, and one that returns publishes as a new instance. A damaged packet is
dropped on its own PID, so video freezes until its next keyframe while the
other tracks carry on. The importer also
logs feed health (stalled streams, TR 101 290 errors) without changing what it
publishes. `--passthrough` publishes the multiplex whole instead, every packet
in order on one [`m2ts`](/concept/hang#transport-streams) track with every
Expand Down
3 changes: 2 additions & 1 deletion doc/bin/srt.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ With the default `--linger 0s`, that catalog must already be available.
A multi-program feed is refused unless `--program`
picks one: `--program 2` imports program 2 alone, and `--program all`
publishes each program as its own broadcast (`event.hang` becomes
`event/1.hang`, `event/2.hang`, and so on).
`event/1.hang`, `event/2.hang`, and so on), following the PAT as programs
join and leave.

```bash
moq --connect https://relay.example.com/anon --broadcast event.hang import srt --listen '[::]:9000' --program all
Expand Down
4 changes: 4 additions & 0 deletions drafts/draft-lcurley-moq-mpegts.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ They are the only service-layer fields this document parses; everything else abo

A publisher MUST include `program` when the broadcast came from a transport stream, and omit it otherwise.
A publisher MUST NOT change `transportStreamId` or `programNumber` in place: the carried `si` describes that service, so a source whose PAT names another ends the broadcast.
A PAT that moves the PMT to another PID changes `pmtPid` in place.

## si {#field-si}
The standalone service information tables, keyed by the PID they ride on and then by `table_id`:
Expand Down Expand Up @@ -372,6 +373,7 @@ A consumer rebuilding a transport stream:
- MUST place each track on its recorded `pid`, and MUST assign an unused PID to a track with none.
- MUST put the PMT on `program.pmtPid` and build the PAT and PMT from `program`.
- MUST choose a PCR PID and emit PCRs on it at the program clock's repetition interval. With no media stream it MUST use an unused PID with adaptation-only PCR packets and no PMT elementary-stream entry. It MUST NOT refuse a program for lacking video.
- MUST follow the catalog as it changes: when a track joins or leaves `tracks` or `programDescriptors` changes, it MUST emit a PMT at the next `version_number` (modulo 32) before any packet of a stream it adds, and MUST NOT emit a stream's packets after a PMT that no longer lists it. Each version it emits MUST differ from the one before, and no older version may follow a newer one. The PCR PID MUST NOT move until the stream it rides ends. When `program.pmtPid` changes, it MUST emit a PAT at the next `version_number` naming the new PID, and MUST NOT emit the PMT on the new PID before that PAT or on the old one after it.
- MUST re-emit each track's `descriptors` as its ES-level descriptors, and `programDescriptors` as the PMT's `program_info`.
- MUST re-emit each `si` entry's sections byte-for-byte on that entry's PID, reading them from its track ({{si-track}}), at least as often as its `interval` when declared.
- MUST packetize each `verbatim` frame as one section on its track's PID.
Expand Down Expand Up @@ -457,6 +459,8 @@ A broadcast demultiplexed from a DVB transport stream: video and audio described
- A flagged PCR jump on an `m2ts` track follows hang's discontinuity marker rather than a skipped group sequence.
- An `m2ts` track unwraps PCRs from its history, clamps extrapolated object times at a forward discontinuity, and cuts a fallback group after one second. `randomAccess` covers every published group; `muxRate` follows the measured source rate.
- A `tracks` entry is never changed in place: a stream the PMT revises continues under a new name, and one it drops leaves the section. `programDescriptors` follows the PMT; `program`'s `transportStreamId` and `programNumber` do not change.
- A consumer rebuilding a stream follows the catalog's revisions at the next PMT `version_number`, and carries a stream only while a PMT lists it.
- `pmtPid` follows a PAT that moves the PMT, and a consumer rebuilding a stream moves the PMT behind a PAT at the next `version_number`.

## draft-lcurley-moq-mpegts-00
{:numbered="false"}
Expand Down
7 changes: 3 additions & 4 deletions quest/m1/ts-reliability/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ refused means an error or a counted drop.
| CAT, EMM/ECM, ATSC PSIP, other SI PIDs, unlisted PIDs, TS scrambling | Outside the profile, raw lane; counted when the import reads every program. Today dropped uncounted | [routes](/quest/m1/ts-reliability/stream-routes.md) |
| DVB SI on 0x0010, 0x0011, 0x0012, 0x0014 | Preserved: snapshot track per PID and `table_id` | landed |
| Program identity, program and ES descriptors | Preserved: descriptors follow each PMT revision; an identity change refuses | landed |
| PMT add, remove, retype; PID reuse | Normalized without a new epoch: a changed or reused PID ends its track and continues under a fresh name, and the PMT is rebuilt at the next `version_number`. Import landed; today refusal or stale PMT on export | [PMT export](/quest/m1/ts-reliability/pmt-export.md) |
| PAT changes: moved PMT PID, programs added or removed | Normalized: followed; a removed program retires, and returns as a new epoch. Today ignored, or the whole multiplex stops | [PAT changes](/quest/m1/ts-reliability/pat-lifecycle.md) |
| PMT add, remove, retype; PID reuse | Normalized without a new epoch: a changed or reused PID ends its track and continues under a fresh name, and the export rebuilds the PMT at the next `version_number`. Today `export ts --sync` ends at an added track and announces no other revision | [PMT export](/quest/m1/ts-reliability/pmt-export.md) |
| PAT changes: moved PMT PID, programs added or removed | Normalized: a moved PMT is followed, and export moves it behind a PAT at the next `version_number`; under `--program all` an added program publishes, a removed one retires, and one returning publishes a new epoch. A listed program whose PMT never arrives waits, unannounced | landed |
| Programs without video | Preserved when the PMT names a PCR PID; refused without one | landed |
| PCR | Normalized: rebased, carried on the first video PID, else audio, else a dedicated PCR-only PID | landed |
| Mux rate, null stuffing | Normalized: re-padded to `muxRate` while the source is CBR | landed |
Expand All @@ -88,8 +88,7 @@ remain with their current quests.

## Required

- [PMT export](/quest/m1/ts-reliability/pmt-export.md) - a TS export announces each PMT revision at the next `version_number`
- [PAT changes](/quest/m1/ts-reliability/pat-lifecycle.md) - a revised PAT never leaves a broadcast meaning another program
- [PMT export](/quest/m1/ts-reliability/pmt-export.md) - synced TS legs revise their PMT identically
- [Stream routes](/quest/m1/ts-reliability/stream-routes.md) - every PID gets a declared route, and what is not carried is counted
- [Release clock false alarms](/quest/m1/ts-reliability/release-clock-false-alarms.md) - no false jitter leap at a 500 ms delay and no false drift alarm

Expand Down
48 changes: 0 additions & 48 deletions quest/m1/ts-reliability/pat-lifecycle.md

This file was deleted.

71 changes: 29 additions & 42 deletions quest/m1/ts-reliability/pmt-export.md
Original file line number Diff line number Diff line change
@@ -1,54 +1,41 @@
# [M] TS export follows a PMT revision
# [M] Synced TS legs revise their PMT identically

## Goal

A TS export of a broadcast whose layout changes mid-stream announces the
revised layout as an ordinary PMT revision. A track added, retired, or
replaced under a fresh name changes the PMT at the next `version_number`;
nothing else blips.
Two `moq export ts --sync` legs of a broadcast whose layout changes
mid-stream revise their PMT identically: the same `version_number`, in the
same slot, listing the same streams, wherever both legs hold the content
around it. A track added no longer ends a synced export.

## Plan

The import already keeps track names honest: a changed `stream_type` or ES
descriptor loop finishes the old track, drops its catalog entry, and opens
a new uniquely named one; a removed stream finishes its track and leaves
the catalog; `programDescriptors` updates in place; a program identity
change refuses.
Outside `--sync`, the export follows a PMT revision: it rebuilds the PMT at
the next `version_number` when the track set or an entry changes, drops a
retired track's PID once its track ends, and keeps the PCR PID unless its
track retires.

What the export does today:
What `--sync` does today:

- `Export` builds the PAT/PMT once. A track added afterward fails the export
("TS track layout changed after PAT/PMT was emitted"), so every revision
the import publishes ends the export.
- A track that leaves the catalog keeps its PID in the PMT forever, and
`programDescriptors` is read once.
- A switch to another instance already rebuilds the tables at the next
`version_number`.
- The tables are laid once, from the catalog the leg opened on. A track
added afterward fails the export ("… under --sync").
- A track that leaves the catalog keeps its PID and its lane, and an entry
or `programDescriptors` change is not announced.
- So legs that open on either side of a catalog change lay different PMTs,
and a pair does not survive an added stream.

Decided 2026-10-20 by the maintainer:
Decided 2026-10-22 by the maintainer: under `moq export ts --sync`, the
revision is a function of the catalog alone, so identical legs revise
their PMT identically.

- Export rebuilds the PMT at the next `version_number` when the track set
or an entry changes, drops a retired track's PID once its track ends, and
keeps the PCR PID unless its track retires. A TS receiver sees an
ordinary PMT revision and a Hang player sees a rendition switch.
Open: where a revision lands. Each leg reads the catalog update at its own
moment, so the slot that carries the revised PMT, and its `version_number`,
must follow from something both legs read alike, such as an added track's
first unit on its lane or the end of a retired track's last group. A
revision that adds a track also needs a lane the rate can carry.

A `programDescriptors` change is an entry change too: the PMT carries the
revised `program_info` at the next `version_number`.
Rust: `rs/moq-mux` TS export (`with_sync`).

Under `moq export ts --sync`, the revision is a function of the catalog
alone, so identical legs revise their PMT identically.

Rust: `rs/moq-mux` TS export. Draft
([mpegts](/drafts/draft-lcurley-moq-mpegts.md)): the rebuild section says
how a consumer revises the PMT.

Tests use the generated `fixture::Mux` builder through import and export:
PMT add, remove, retype, descriptor-only change, version rollover (31 to
0), PID reuse after removal, and a program descriptor change, each
asserting the exported PMT's streams and version sequence. Extend the
TSDuck round trip with a generated mid-stream PMT revision so
`compliance.py` checks the rebuilt version sequence in CI.

## Related

- [PAT changes](/quest/m1/ts-reliability/pat-lifecycle.md) - a moved PMT PID reaches the export as a new PAT
Tests: two synced legs, joined apart, across a PMT add, remove, retype and
descriptor change, write the same packets from the revision on, PMTs
included; a leg that joins after a revision lays the revised PMT at the
same `version_number`.
Loading