Internet-Draft moq-mpegts September 2026
Curley Expires 28 March 2027 [Page]
Workgroup:
moq
Internet-Draft:
draft-lcurley-moq-mpegts-00
Published:
Intended Status:
Informational
Expires:
Author:
L. Curley

MoQ MPEG-TS Catalog Extension

Abstract

This document defines the mpegts catalog section, which records what demultiplexing an MPEG-2 Transport Stream [mpeg2] 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 [hang] or the MSF catalog [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.

Status of This Memo

This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.

Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.

Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."

This Internet-Draft will expire on 28 March 2027.

▲

Table of Contents

1. 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 [mpeg2] terminology for PID, PSI, PAT, PMT, PES, descriptor, section, and stream_type, and [hang] terminology for broadcast, track, group, and frame.

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

2. 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 [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 (Section 3).

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 Section 5.

3. Comparison with m2ts Packaging

[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:

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

Table 1
  m2ts packaging mpegts section
MoQ track one per program one per elementary stream
Object payload whole source packets one media frame, or one verbatim PES payload or section
Packaging m2ts none defined; media keeps its own
Catalog m2ts* members on each track one mpegts root member
Relay caches and drops whole objects drops and prioritizes per elementary stream

The two overlap only on program identity: m2tsProgramNumber and m2tsPmtPid against Section 4.3, and m2tsPsiInterval against a per-PID interval (Section 4.4). 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.

4. The mpegts Section

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

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.

4.1. 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 (Section 4.6), in PMT order.

verbatim marks a track carrying an undecoded elementary stream (Section 5). 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.

4.2. programDescriptors

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

4.3. 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.

4.4. 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 ([mpeg2] 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 (Section 4.5.1), 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 [dvbrep] 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.

4.5. 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 (Section 7).

4.5.1. 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.

4.6. 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 ([RFC4648], Section 4).

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

5. 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 [scte35], 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 ([hang]): a varint microsecond timestamp followed by the payload.

6. Carriage

The section is the same JSON in either catalog format.

6.1. hang

A root member named mpegts, alongside video and audio ([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.

6.2. MSF

A root member named mpegts, alongside tracks ([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; [msfts] registers m2ts for the passthrough shape (Section 3).

7. Rebuilding a Transport Stream

A consumer rebuilding a transport stream:

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.

8. 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.

9. IANA Considerations

This document requests no registrations.

Should [msf] establish a registry of catalog root members, this document requests registration of mpegts with this document as the reference.

10. References

10.1. Normative References

[hang]
Curley, L., "Media over QUIC - Hang", Work in Progress, Internet-Draft, draft-lcurley-moq-hang-02, , <https://datatracker.ietf.org/doc/html/draft-lcurley-moq-hang-02>.
[mpeg2]
ISO/IEC, "Information technology - Generic coding of moving pictures and associated audio information: Systems", ISO/IEC 13818-1, , <https://www.iso.org/standard/83239.html>.
[msf]
Law, W. and S. Nandakumar, "MOQT Streaming Format", Work in Progress, Internet-Draft, draft-ietf-moq-msf-01, , <https://datatracker.ietf.org/doc/html/draft-ietf-moq-msf-01>.
[RFC2119]
Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, , <https://www.rfc-editor.org/rfc/rfc2119>.
[RFC4648]
Josefsson, S., "The Base16, Base32, and Base64 Data Encodings", RFC 4648, DOI 10.17487/RFC4648, , <https://www.rfc-editor.org/rfc/rfc4648>.
[RFC8174]
Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, , <https://www.rfc-editor.org/rfc/rfc8174>.

10.2. Informative References

[dvbrep]
ETSI, "Digital Video Broadcasting (DVB); Guidelines on implementation and usage of Service Information (SI)", ETSI TS 101 211, <https://www.etsi.org/standards>.
[msfts]
Gregoire, P. and G. Simon, "MPEG-2 Transport Stream Packaging for MOQT", Work in Progress, Internet-Draft, draft-gregoire-moq-msfts-01, , <https://datatracker.ietf.org/doc/html/draft-gregoire-moq-msfts-01>.
[scte35]
SCTE, "Digital Program Insertion Cueing Message", SCTE 35, <https://www.scte.org/standards/>.

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.

Author's Address

Luke Curley