MoQ is under active development. APIs and protocols may change between releases.

Skip to content

MoQ MPEG-TS Catalog Extension ​

INFO

Rendered from the Internet-Draft source in this repository. Submitted versions are on the IETF datatracker.

Abstract ​

This document defines the mpegts catalog section, which records what demultiplexing an MPEG-2 Transport Stream Information technology - Generic coding of moving pictures and associated audio information: Systems into a MoQ broadcast would otherwise lose: each track's PID and PMT descriptors, the program identity, the service information tables, and a carriage record for every elementary stream the publisher did not decode. It is a root member of either the hang catalog draft-lcurley-moq-hang or the MSF catalog draft-ietf-moq-msf, so a subscriber that ignores it still plays the broadcast and one that reads it can rebuild the source multiplex.

Note to Readers ​

This document was generated by an AI model from the implementation at github.com/moq-dev/moq and is maintained alongside it. Submit an issue or PR if this spec sucks and you want to fix anything.

Conventions and Definitions ​

The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in BCP 14 RFC2119 RFC8174 when, and only when, they appear in all capitals, as shown here.

This document uses Information technology - Generic coding of moving pictures and associated audio information: Systems terminology for PID, PSI, PAT, PMT, PES, descriptor, section, and stream_type, and draft-lcurley-moq-hang terminology for broadcast, track, group, and frame.

A verbatim track carries an elementary stream the publisher did not decode, byte-for-byte.

Introduction ​

A transport stream reaches MoQ in one of two shapes. A publisher can leave the multiplex intact and carry the packet stream as opaque payload, which is what draft-gregoire-moq-msfts specifies. Or it can demultiplex, which is what this document addresses: each elementary stream becomes its own MoQ track with a codec description, so a relay can drop and prioritize per track and a subscriber can decode without a transport-stream parser (Comparison with m2ts Packaging).

Demultiplexing loses everything that is not media: the PID layout, the PMT descriptors, the program identity, the service information, and any stream the publisher has no decoder for. The mpegts section records that. It defines no packaging and no container: decoded media keeps the container its catalog entry declares, and verbatim tracks use the framing in Verbatim Tracks.

Comparison with m2ts Packaging ​

draft-gregoire-moq-msfts carries the transport stream as it arrives: each object is a run of whole 188- or 192-byte packets, and the receiver demultiplexes as it always has. This document takes the stream apart at the publisher and discards the packetization layer, because MoQ already provides what it was there for:

  • Packet framing interleaves elementary streams onto one serial pipe. MoQ delivers each track separately.
  • Null packets pad a multiplex to a constant bitrate. There is no bitrate to pad to.
  • The PCR distributes the multiplex's timebase. Each track carries its own timestamps.
  • Continuity counters detect loss within a stream. That is the transport's job.
  • PSI repetition lets a receiver join mid-stream. The catalog is a track a subscriber fetches on join.

What remains is media in a codec-neutral catalog, plus the signaling a demultiplexer cannot reconstruct: that is the mpegts section.

m2ts packagingmpegts section
MoQ trackone per programone per elementary stream
Object payloadwhole source packetsone media frame, or one verbatim PES payload or section
Packagingm2tsnone defined; media keeps its own
Catalogm2ts* members on each trackone mpegts root member
Relaycaches and drops whole objectsdrops and prioritizes per elementary stream

The two overlap only on program identity: m2tsProgramNumber and m2tsPmtPid against program, and m2tsPsiInterval against a per-PID interval (si). They do not collide, since those members sit on a track and this section sits at the root. A track is one shape or the other.

The mpegts Section ​

A publisher that demultiplexed a transport stream SHOULD add an mpegts root member to its catalog (Carriage):

type Mpegts = {
  "tracks": Map<TrackName, Track> | undefined,
  "programDescriptors": Descriptor[] | undefined,
  "program": Program | undefined,
  "si": Map<PidString, Si> | undefined,
  "muxRate": number | undefined,
}

Every member is optional and a publisher MUST omit an empty one, so a broadcast with nothing to record omits the section entirely.

tracks ​

tracks maps a MoQ track name to that track's MPEG-TS identity:

type Track = {
  "pid": number,
  "descriptors": Descriptor[] | undefined,
  "verbatim": Verbatim | undefined,
}

pid is the track's PID in the source, 0..8191. A publisher MUST NOT record 0x0000 (PAT) or 0x1FFF (null packets), which carry no elementary stream.

descriptors are the track's ES-level descriptors from the PMT (Descriptor), in PMT order.

verbatim marks a track carrying an undecoded elementary stream (Verbatim Tracks). A decoded track MAY appear here too, to preserve its pid and descriptors. A consumer MUST ignore an entry with neither a verbatim record nor a track the catalog describes.

programDescriptors ​

The PMT's program-level descriptors (program_info), in PMT order (Descriptor).

program ​

The program identity from the PAT:

type Program = {
  "transportStreamId": number,
  "programNumber": number,
  "pmtPid": number,
}

transportStreamId, programNumber (the DVB service id), and pmtPid come from the PAT. They are the only service-layer fields this document parses; everything else about the program stays inside the opaque si tables.

A publisher MUST include program when the broadcast came from a transport stream, and omit it otherwise.

si ​

The standalone service information tables, keyed by the PID they ride on and then by table_id:

type Si = Map<TableIdString, SiEntry>

type SiEntry = {
  "track": string,
  "interval": number | undefined,
}

JSON object keys are strings, so both are decimal with no leading zeros: "17" for PID 0x0011, "66" for table_id 0x42. A consumer MUST refuse a catalog whose PID key is not an integer in 0..8191, or whose table_id key is not an integer in 0..255. A consumer MAY additionally accept the pre-table_id form, where a PID maps to {"interval", "sections"} with the sections inline: it decodes into one entry per table_id (byte 0 of each section) naming no track. A producer MUST NOT write that form.

table_id is byte 0 of generic section syntax (Information technology - Generic coding of moving pictures and associated audio information: Systems Section 2.4.4), so the key is no less generic than the PID; which ranges mean what is a delivery-system convention this document does not rely on.

track names the snapshot track carrying the entry's sections (SI Track), published in the same broadcast as the catalog. The sections are not in the catalog: a full EPG is megabytes and would be republished on every unrelated catalog change.

interval bounds how often a rebuilt stream repeats the entry's sections, in milliseconds. A publisher SHOULD use its delivery system's maximum repetition interval for that table_id, for example Digital Video Broadcasting (DVB); Guidelines on implementation and usage of Service Information (SI) for DVB: 2000 for SDT actual and EIT present/following actual, 10000 for the NIT, the BAT, SDT other and EIT schedule actual, 30000 for EIT schedule other. It MUST omit interval for a table_id whose requirement it does not know; a consumer then falls back to its own PSI cadence. The key is table_id rather than PID because that is the granularity the requirements are defined at: one PID carries tables wanting different rates.

PAT and PMT are never carried here: they are rebuilt from program, programDescriptors, and the per-track entries.

muxRate ​

The rate the source's PCR clock paced the whole multiplex at, in bits per second: every PID, the PSI, and the null packets, measured as the packets between two PCRs over the time they span. It is not a sum of the elementary streams, and a track's own bitrate keeps its codec meaning.

A publisher MUST include muxRate only while the source holds a constant rate, and MUST omit it for a variable-rate or unpaced source; a value that has become invalid is removed rather than left stale. A consumer rebuilding a transport stream SHOULD pad its output with null packets to muxRate (Rebuilding a Transport Stream).

SI Track ​

Each group is a complete picture of the entry's current sections: one frame per sub-table, each frame that sub-table's sections concatenated verbatim in section_number order. Sections are self-delimiting through section_length, so a frame needs no further framing. A joiner reads only the newest group.

Frames apply in order and a later frame replaces an earlier one of the same sub-table, so a publisher MAY append a revision within a group rather than opening a new one. A publisher MUST commit a revised sub-table whole: no group may expose part of one version beside part of another.

A sub-table is identified by its table_id_extension, plus the bytes that disambiguate it where the extension alone does not: original_network_id for SDT other, and transport_stream_id with original_network_id for the EIT, whose extension is a service id unique only within a transport stream. A short-form section carries no extension, version, or numbering, so its table_id is one latest-value slot and each arrival replaces the last; that is what lets a time table be proxied rather than synthesized.

Descriptor ​

One PMT descriptor:

type Descriptor = {
  "tag": number,
  "data": string,
}

tag is the descriptor_tag (0x05 registration, 0x0A ISO-639 language, ...), 0..255. data is the body after the tag and length, base64 (RFC 4648, Section 4).

Descriptors are never parsed, so a consumer MUST re-emit a list in order and MUST NOT reorder, merge, or drop entries.

Verbatim Tracks ​

An elementary stream the publisher does not decode rides its own MoQ track, byte-for-byte:

type Verbatim = {
  "streamType": number,
  "framing": "pes" | "section",
  "streamId": number | undefined,
}

streamType is the PMT stream_type to re-announce: 0x86 for SCTE-35 Digital Program Insertion Cueing Message, 0x06 for private PES, 0x05 for private sections.

framing says how the payload is framed, defaulting to "pes". With "pes" each frame is one complete PES payload, timestamped with its PTS, or 0 if it carried none. With "section" each frame is one complete section, header and CRC included, timestamped with the media time it arrived at. A consumer MUST refuse a catalog that names a framing it does not recognize, rather than guess.

streamId is the original PES stream_id, for example 0xBD (private_stream_1) for teletext, DVB subtitles, and DVB AC-3. It applies only to "pes" framing. A publisher SHOULD record it, since strict demultiplexers reject a relabeled PES; a consumer with none falls back to private_stream_1.

Each frame MUST be a keyframe in its own group: a verbatim payload is all-or-nothing.

A verbatim track has no codec, dimensions, or decoder configuration, so a publisher MUST NOT describe it as a media rendition and MUST describe it only by its tracks entry. Nothing there declares a container, so its frames use hang's legacy container (draft-lcurley-moq-hang): a varint microsecond timestamp followed by the payload.

Carriage ​

The section is the same JSON in either catalog format.

hang ​

A root member named mpegts, alongside video and audio (draft-lcurley-moq-hang). hang carries a decoder config's raw bytes as hex while every binary field here is base64; the alphabets overlap, so the encoding cannot be detected and is stated per field above.

MSF ​

A root member named mpegts, alongside tracks (draft-ietf-moq-msf). A publisher MUST NOT name a section after a member MSF itself defines.

The keys of the section's own tracks member are MSF track names; a decoded track also has an MSF track object, a verbatim track does not.

This document defines no packaging value; draft-gregoire-moq-msfts registers m2ts for the passthrough shape (Comparison with m2ts Packaging).

Rebuilding a Transport Stream ​

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 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 repacketize each verbatim track per its framing and streamType, using streamId when recorded.
  • SHOULD pad the output with null packets to muxRate when present, so the rebuilt stream is constant-rate again; a source that exceeds the rate is passed through rather than delayed or dropped.

With no program the consumer synthesizes an identity, and SHOULD then omit any carried si, which describes a program that no longer exists.

A consumer MAY derive implied signaling: a program carrying a section-framed 0x86 stream and no programDescriptors implies the SCTE-35 CUEI registration descriptor. A consumer MUST NOT derive one that contradicts a recorded descriptor.

Security Considerations ​

Every binary field here is re-emitted without inspection, by a publisher that never parsed it and a relay that never looked, so a consumer MUST treat all of it as untrusted input.

Nothing bounds the number of entries in the section, nor the size of an SI track: a publisher controls both, and an EPG is large by nature. A consumer MUST bound what it accepts of each, and MUST reject a catalog it cannot bound rather than truncate it into a stream that silently differs from what was described. PIDs, program numbers, and stream types are 13-, 16-, and 8-bit values in a JSON document that can hold any number, so a consumer MUST range-check each one. A catalog can also name one PID twice, so a consumer MUST NOT emit a stream in which two elementary streams share a PID, or in which an SI table shares a PID with PAT, PMT, null packets, or an elementary stream.

The section makes a broadcast's provenance legible to anything that can read the catalog, relays included: the service name, provider, and network inside the carried SI, and the original PID layout. A publisher that does not want that MUST omit the section.

IANA Considerations ​

This document requests no registrations.

Should draft-ietf-moq-msf establish a registry of catalog root members, this document requests registration of mpegts with this document as the reference.

Normative References ​

Informative References ​

Appendix A: Example ​

A broadcast demultiplexed from a DVB transport stream: video and audio described as ordinary renditions, a verbatim SCTE-35 stream, and the source's SDT Actual (table_id 0x42 on PID 0x0011) carried opaquely on its own track.

{
  "mpegts": {
    "program": {
      "transportStreamId": 4660,
      "programNumber": 1,
      "pmtPid": 100
    },
    "programDescriptors": [
      { "tag": 5, "data": "Q1VFSQ==" }
    ],
    "tracks": {
      "video0": { "pid": 257 },
      "audio0": {
        "pid": 258,
        "descriptors": [ { "tag": 10, "data": "ZW5nAA==" } ]
      },
      "0.ts": {
        "pid": 500,
        "verbatim": { "streamType": 134, "framing": "section" }
      }
    },
    "si": {
      "17": {
        "66": { "track": "si/17/66", "interval": 2000 }
      }
    }
  }
}

Appendix B: Changelog ​

draft-lcurley-moq-mpegts-00 ​

  • Initial version.
  • The Si type is keyed by table_id only; the PID lives on the enclosing si map.
  • A consumer refuses a catalog with an unrecognized framing or an invalid si map key.
  • Added muxRate, the source's constant multiplex rate.
  • A consumer may read the pre-table_id inline sections form; writing stays track-only.

Acknowledgments ​

This document was drafted with the assistance of Claude, an AI assistant by Anthropic.

Licensed under MIT or Apache-2.0